First we need to set up the environment and load the packages we will use for this workshop.

library(Seurat): Loads the Seurat package, which is a comprehensive toolkit for single-cell RNA sequencing and spatial transcriptomics data analysis. It provides a wide range of functions for data preprocessing, normalization, clustering, dimensionality reduction, and visualization. Explore documentation here: https://satijalab.org/seurat/

library(ggplot2): Loads the ggplot2 package, a powerful and flexible system for creating static visualizations in R. Explore documentation here: https://ggplot2.tidyverse.org/

library(scCustomize): Loads the scCustomize package, which provides custom functions and themes to enhance the visualization and analysis capabilities of single-cell and spatial transcriptomics data, often in conjunction with Seurat. Explore documentation here: https://samuel-marsh.github.io/scCustomize/

library(readr): Loads readr package for fast and friendly reading of rectangular data, such as CSV files, into R.

library(pheatmap): Loads pheatmap package, which is for creating pretty heatmaps, offering better control over heatmap customization compared to base R.

library(matrixStats): matrixStats provides highly optimized functions for matrix operations, particularly useful for computing row and column summaries.

library(spdep): spdep stands for Spatial Dependence and Spatial Autocorrelation, and it provides functions for spatial data analysis, including spatial weights generation, spatial autocorrelation statistics, and spatial regression.

library(geojsonR) The geojsonR library is used for handling GeoJSON data in R. GeoJSON is a format for encoding a variety of geographic data structures using JavaScript Object Notation (JSON). It is sometimes used as a format for storing cell segmentation boundaries.

library(Seurat)
Loading required package: SeuratObject
Loading required package: sp
The legacy packages maptools, rgdal, and rgeos, underpinning the sp package,
which was just loaded, will retire in October 2023.
Please refer to R-spatial evolution reports for details, especially
https://r-spatial.org/r/2023/05/15/evolution4.html.
It may be desirable to make the sf package available;
package maintainers should consider adding sf to Suggests:.
The sp package is now running under evolution status 2
     (status 2 uses the sf package in place of rgdal)

Attaching package: ‘SeuratObject’

The following objects are masked from ‘package:base’:

    intersect, t

Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(ggplot2)
library(scCustomize)
scCustomize v2.1.2
If you find the scCustomize useful please cite.
See 'samuel-marsh.github.io/scCustomize/articles/FAQ.html' for citation info.
library(readr)
library(pheatmap)
library(matrixStats)
library(spdep)
Loading required package: spData
To access larger datasets in this package, install the spDataLarge
package with: `install.packages('spDataLarge',
repos='https://nowosad.github.io/drat/', type='source')`
Loading required package: sf
Linking to GEOS 3.10.2, GDAL 3.4.1, PROJ 8.2.1; sf_use_s2() is TRUE
library(geojsonR)

Sets the path to the directory containing the Xenium output data - this is the directory where all of the outputs are stored.

data_dir <- "/project/shared/spatial_data_camp/datasets/DATASET2/XENIUM_COLORECTAL_CANCER"

ReadXenium reads Xenium spatial transcriptomics data from a specified directory using a Seurat wrapper function that supports this data format. Xenium data typically includes expression matrices and spatial coordinates, along with other information about cell centroids and segmentations and coordinates of individual transcripts.

data_dir: The path to the directory containing the Xenium data, set in the previous step. outs = c(“matrix”, “microns”): Specifies the outputs to read from the data directory. matrix refers to summarised cell by gene matrix and microns refers to individual transcript coordinates.

type = c(“centroids”, “segmentations”): Indicates the types of spatial information to include - here, we are reading ib both cell centroid coordinates and cell boundary segmentations.

data <- ReadXenium(data_dir, outs = c("matrix", "microns"), type=c("centroids", "segmentations"))
10X data contains more than one type and is being returned as a list containing matrices of each type.
|--------------------------------------------------|
|==================================================|

This provides us a list of data:

names(data)
[1] "matrix"        "microns"       "centroids"     "segmentations"

Matrix is further split into gene expression matrix and various control probes and codewords. Different platforms and platform versions include different control probes. As this will vary, it’s important to check and understand what the specific controls in your own data are.

Here, negative control probes are probes that are added to the reaction but target non-biological sequences and should not bind any tissue RNA. Negative control codewords are valid codewords, but no probes with that codeword added to the reaction. This effectively tells us how good the transcript calling algorithm is.

names(data$matrix)
[1] "Gene Expression"           "Negative Control Codeword"
[3] "Negative Control Probe"    "Unassigned Codeword"      

Read in additional information about the cells - this gives us pre-calculated information, for example segmented cell or nucleus size for each cell.

cell_meta_data <- read.csv(file.path(data_dir, "cells.csv.gz"))
rownames(cell_meta_data) <- cell_meta_data$cell_id
head(cell_meta_data)

We will start by creating a basic seurat object from the data.

CreateSeuratObject function initializes a Seurat object using the provided gene expression matrix and optional metadata.

counts: The gene expression matrix, which contains the raw count data for each gene in each cell. data$matrix[[“Gene Expression”]]: Specifies the gene expression matrix extracted from the loaded Xenium data. Here, we leave out the control probes for now.

assay: The name of the assay - you can call it anything you like. Here, we go with “XENIUM”.

meta.data: Metadata associated with the cells or spots. Here, we add the cell statistics we read in earlier as cell_meta_data.

By printing the seurat object, we can see that we read in ~ 30,000 cells with measures for 325 genes

seurat <- CreateSeuratObject(counts = data$matrix[["Gene Expression"]],
Warning message:
In png(..., res = dpi, units = "in") :
  unable to open connection to X11 display ''
                                 assay = "XENIUM",
                                 meta.data = cell_meta_data)
seurat
An object of class Seurat 
325 features across 647524 samples within 1 assay 
Active assay: XENIUM (325 features, 0 variable features)
 1 layer present: counts

Adding spatial coordinates to a Seurat object allows for spatially resolved analysis and visualization. This requires creating objects for centroids and segmentations we read in earlier, and then integrating these with the main Seurat object.

CreateFOV: This function creates a field of view (FOV) object that includes spatial information about the centroids, segmentations, and molecule coordinates. An FOV can be the entire slide, or a selected region within a slide - i.e. it does not need to have entries for all the cells in the seurat object.

coords: A list containing the centroids and/or segmentation data. For larger datasets, it can be quicker to only load centroids, as this minimises the amount of data points.

centroids = CreateCentroids(data\(centroids)*: Creates a centroids object from the centroid data in the Xenium dataset. *segmentation = CreateSegmentation(data\)segmentations): Creates a segmentation object from the segmentation data in the Xenium dataset.

type = c(“segmentation”, “centroids”): Specifies the types of spatial data being included, which are segmentation and centroid data.

molecules = data$microns: The spatial coordinates of individual transcripts/molecules in the data. This is optional - for larger datasets, skipping transcript coordinates can be a good idea.

seurat[[“COLON”]] <- coords: Adds the created FOV object to the Seurat object under the new FOV name “COLON”. This can be named (almost) anything - but, avoid using underscores as this can create some unexpected behaviours later.

TIP: LoadXenium() is a wrapper that would load in both cell counts matrix and spatial coordinates in one function, simplifying these steps. However, in situ platforms are evolving at a very fast rate and there are constant changes on how the data is stored, in particular for file formats for cell segmentation and coordinates. Here, we have broken down the steps to show how to assemble an in situ seurat object from the key components, in case the platform specific readers don’t work for your specific data.

coords <- CreateFOV(coords = list(centroids = CreateCentroids(data$centroids), 
                                  segmentation = CreateSegmentation(data$segmentations)),
                    type = c("segmentation", "centroids"),
                    assay = "XENIUM")
seurat[["COLO_CAN"]] <- coords  

Inspect the object - now, you can see we have added a spatial field of view:

seurat
An object of class Seurat 
325 features across 647524 samples within 1 assay 
Active assay: XENIUM (325 features, 0 variable features)
 1 layer present: counts
 2 spatial fields of view present: COLO_CAN CRC1
#crop the image to check which coordinates are needed
cropped <- Crop(seurat[["COLO_CAN"]], x = c(4000, 2000), y = c(1000, 4000), coords = "plot")
seurat[["CRC1"]] <- cropped
Warning: Key ‘XENIUM_’ taken, using ‘crc1_’ instead
ImageDimPlot(seurat,fov = "CRC1", axes = TRUE)




seurat_CRC1 <- seurat[,seurat$x_centroid < 4000 & seurat$x_centroid > 2000 &
                        seurat$y_centroid <4000 & seurat$y_centroid > 1000]
Warning: Not validating Centroids objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating Seurat objectsWarning: Not validating Centroids objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating Seurat objects
ImageDimPlot(seurat_CRC1, axes=TRUE)

Adding control probes and codewords as separate assays in the Seurat object allows for the tracking and analysis of technical artifacts and noise within your spatial transcriptomics data, while keeping these outputs separate from the main biological gene expression values.

Unassigned codewords are unused codewords. There is no probe in a particular gene panel that will generate the codeword.

Negative control probes are probes that exist in the panels but target non-biological sequences. They can be used to assess the specificity of the assay.

Negative control codewords are codewords in the codebook that do not have any probes matching that code. They are chosen to meet the same requirements as regular codewords and can be used to assess the specificity of the decoding algorithm.

seurat_CRC1[["Negative.Control.Codeword"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Codeword"]][, WhichCells(seurat_CRC1)])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
seurat_CRC1[["Negative.Control.Probe"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Probe"]][, WhichCells(seurat_CRC1)])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
seurat_CRC1[["Unassigned.Codeword"]] <- CreateAssayObject(counts = data$matrix[["Unassigned Codeword"]][, WhichCells(seurat_CRC1)])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')

Inspect the object:

seurat_CRC1
An object of class Seurat 
541 features across 14054 samples within 4 assays 
Active assay: XENIUM (325 features, 0 variable features)
 1 layer present: counts
 3 other assays present: Negative.Control.Codeword, Negative.Control.Probe, Unassigned.Codeword
 1 spatial field of view present: COLO_CAN

Let’s start with some basic QC and visualisation of the data.

In Seurat, in situ spatial transcriptomics counterpart functions to ‘SpatialDimPlot’ and ‘SpatialFeaturePlot’ we covered yesterday are called ‘ImageFeaturePlot’ and ‘ImageDimPlot’. These have additional functionality to plot cell segmentations and individual transcript coordinates, but otherwise function exactly the same as the sequencing based ST counterparts.

First, lets visualise the total transcripts detected per cell.

As in scRNA-Seq data, this is the most basic measure of overall signal and how well the data looks.

Unlike in scRNA-Seq data or unbiased sequencing-based ST, these measures are also very heavily dependent not only on the total RNA quantity of each cell and tissue quality, but also on the target panel used for the experiment. Under-represented cell types will naturally yield fewer transcripts. Finally, the quality of cell segmentation also plays a role.

In this case, we can see that there are areas with higher and lower total transcripts detected.

Understanding your tissue and target panel here is important to delineate where these differences are biological and where they may be technical.

ImageFeaturePlot(seurat_CRC1, "nCount_XENIUM") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Similarly, we can visualise the total number of gene detected per cell. You can see that this is a bit less variable across tissue.

ImageFeaturePlot(seurat_CRC1, "nFeature_XENIUM") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

This code examines the distribution of the number of features (genes) detected per cell in the Seurat object using a density plot and calculates specific quantiles of this distribution. This is important for understanding the variability and distribution of detected features, which can help identify potential issues such as low-quality cells and determine any filtering thresholds that may need to be applied.

If you’re coming from scRNA-Seq work, these low numbers probably look very alarming. How can you possibly work with 31 median genes per cell?

Unlike scRNA-Seq data and sequencing-based ST, both gene dropouts and noise are much, much lower in in situ ST data.

We are also working with 100-fold fewer targetted genes.

ggplot(seurat[[]], aes(nFeature_XENIUM)) + geom_density()

quantile(seurat$nFeature_XENIUM, c(0.01, 0.1, 0.5, 0.9, 0.99))
 1% 10% 50% 90% 99% 
  5  14  32  54  73 

Using ImageFeaturePlot to visualize the cell area in spatial transcriptomics data allows us to examine the spatial organization and potential heterogeneity of cell sizes within your tissue sample.

Why do we get such a difference in spatial distribution of cell sizes?

This could be due to biological differences between small and large cells - e.g. small cells like T-cells.

However, here the signal correlates with areas of low cellularisation. Therefore, it is likely this is an artefact of nuclei expansion in cell segmentation.

What is Nuclei Expansion?

Nuclei expansion in cell segmentation refers to the process of enlarging the segmented nuclei regions to approximate the boundaries of the entire cells. This technique is used to better represent the actual cell boundaries when only the nuclei have been explicitly segmented/we only have DAPI and no additional cell boundary staining. The primary goal is to provide a more accurate estimation of the cellular area, which is crucial for various downstream analyses in spatial transcriptomics and single-cell studies. In this case, nuclei expansion is constrained either by maximum distance or other nearby cells - so, where there are no other nearby cells to “bump into”, the expansion generates artificially bigger cells.

ImageFeaturePlot(seurat_CRC1, "cell_area", axes = T) + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

We can further check that this is likely the case by plotting the ratio between nuclei and total cell area. We can see that there is a very big decrease in percentage of cell area occupied by nucleus in areas of low cell density.

The cell-to-nucleus area ratio can also potentially provide insights into cell morphology, cell type and potential changes in cellular states or conditions. For example, T-Cells can often be quite well identified by this variable alone, as they have a small cytoplasm volume. However, without a cell boundary stain, this metric mainly captures segmentation artefacts, so be careful about over-interpretation!

seurat_CRC1$cell_nucleus_ratio <- seurat_CRC1$nucleus_area / seurat_CRC1$cell_area
ImageFeaturePlot(seurat_CRC1, "cell_nucleus_ratio") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

If we look at the distribution, we see that we have a big tail end of overly large cells.

ggplot(seurat_CRC1[[]], aes(cell_area)) + geom_density()

In this case, we can see that as expected, there is generally a correlation between cell area and transcript detection rate.

However, we also have a group of cells where this is not the case - very large cells but relatively few transcripts. These cells are mainly submucosal stromal cells which are very poorly covered by the panel 10x have used.

ggplot(seurat_CRC1[[]], aes(nCount_XENIUM, cell_area)) + geom_point() 

We can create a filter to remove the overly large cells from the analysis.

quantile(seurat$cell_area, 0.99): Calculates the 99th percentile of the cell_area values in the Seurat object. This value serves as a threshold to identify the largest 1% of cells - but what is a sensible threshold, if any, depends on your tissue.

seurat\(cell_area < quantile(seurat\)cell_area, 0.99): Compares each cell’s area to the 99th percentile threshold. The result is a logical vector where each element is TRUE if the corresponding cell’s area is less than the 99th percentile and FALSE otherwise.

seurat[[“SIZE_FILTER_LARGE”]]: Creates a new metadata field named SIZE_FILTER_LARGE in the Seurat object, storing the logical vector.

seurat_CRC1[["SIZE_FILTER_LARGE"]] <- seurat_CRC1$cell_area < quantile(seurat_CRC1$cell_area, .99)

Now we can use ImageDimPlot to visualise the cells which have been flagged for removal.

We can see that these are mostly in the submucosa region.

How do different thresholds behave? Is there a more appropriate one to use? Is any necessary at all?

ImageDimPlot(seurat_CRC1, group.by="SIZE_FILTER_LARGE")

We can use the same approach to create a filter for segmented cells which are very small and likely segmentation arfetacts.

quantile(seurat$cell_area, 0.01): Calculates the 1st percentile of the cell_area values in the Seurat object. This value serves as a threshold to identify the smallest 1% of cells.

seurat\(cell_area > quantile(seurat\)cell_area, 0.01): Compares each cell’s area to the 1st percentile threshold. The result is a logical vector where each element is TRUE if the corresponding cell’s area is greater than the 1st percentile and FALSE otherwise.

seurat[[“SIZE_FILTER_SMALL”]]: Creates a new metadata field named SIZE_FILTER_SMALL in the Seurat object, storing the logical vector.

seurat_CRC1[["SIZE_FILTER_SMALL"]] <- seurat_CRC1$cell_area > quantile(seurat$cell_area, .01)

Now we can use ImageDimPlot to visualise the cells which have been flagged for removal.

We can see that these are more scattered throughout the tissue - but there may be more in the follicular regions.

How do different thresholds behave? Is there a more appropriate one to use? Is any necessary at all?

ImageDimPlot(seurat_CRC1, group.by="SIZE_FILTER_SMALL")

We can check how these values correlate with gene detection rate.

If we filter out small cells, we will remove cells with low numbers of genes detected.

If we filter out large cells, this is not that biased towards overly large counts, as we saw before.

p1 <- VlnPlot(seurat_CRC1, "nFeature_XENIUM", group.by = "SIZE_FILTER_SMALL", pt.size = .1, alpha = .5) + labs(title="Small Cell Filter")
Warning: Default search for "data" layer in "XENIUM" assay yielded no results; utilizing "counts" layer instead.
p2 <- VlnPlot(seurat_CRC1, "nFeature_XENIUM", group.by = "SIZE_FILTER_LARGE", pt.size = .1, alpha = .5)+ labs(title="Large Cell Filter")
Warning: Default search for "data" layer in "XENIUM" assay yielded no results; utilizing "counts" layer instead.
p1 + p2

Adjusting the threshold for what is considered a “small cell” can have significant implications for your analysis, especially in areas with specific cell types such as T-cells, which are small and densely packed in follicular regions. This example demonstrates how changing the threshold to the 10th percentile affects the filtering. In this case, we would probably filter out a lot of good cells that we don’t want to lose! So, be careful when looking at these types of QC metrics!

seurat_CRC1[["SIZE_FILTER_SMALL"]] <- seurat_CRC1$cell_area > quantile(seurat_CRC1$cell_area, .1)
ImageDimPlot(seurat_CRC1, group.by="SIZE_FILTER_SMALL")

Lets set this back to the original 1% threshold.

seurat_CRC1[["SIZE_FILTER_SMALL"]] <- seurat_CRC1$cell_area > quantile(seurat_CRC1$cell_area, .01)

The most important filter is the overall transcript detection. Empty cells or cells with very low transcript count cannot be taken forward for clustering analysis and it is extremely difficult to identify what they may be. Here, we set a threshold of minimum 15 transcripts. This seems quite low - for data from in situ platforms with low noise (Xenium, Merfish, Merscope), this is generally enough to cluster and identify cell types. If your data has more noise (e.g. CosMx), a higher threshold is more appropriate.

seurat\(nCount_XENIUM >= 15*: Compares each cell's transcript count to the threshold of 15. The result is a logical vector where each element is TRUE if the corresponding cell has at least 15 transcripts and FALSE otherwise. *seurat\)TRANSCRIPT_FILTER: Creates a new metadata field named TRANSCRIPT_FILTER in the Seurat object, storing the logical vector.

seurat_CRC1$TRANSCRIPT_FILTER <- seurat_CRC1$nCount_XENIUM >= 15

And we can visualise the cells that we would lose.

We see that we disproportionately would filter out more cells from some regions than others. As pointed out previously, this is likely due to a combination of gene panel coverage in some regions and very small cells in densely packed regions like follicles.

ImageDimPlot(seurat_CRC1, group.by="TRANSCRIPT_FILTER")

Finally, visualizing the counts of negative control codewords, negative control probes, and unassigned codewords helps identify and understand technical artifacts and background noise in your spatial transcriptomics data.

Here, we can see that all control probes and codewords produce yield very little signal, suggesting our data is good quality!

In some cases, high amount of autoflourescence is the cells/tissue can sometimes generate false positive signal and this should be filtered out.

ImageFeaturePlot(seurat_CRC1, "nCount_Negative.Control.Codeword") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC1, "nCount_Negative.Control.Probe") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC1, "nCount_Unassigned.Codeword") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Although the negative control signal is low, we can nonetheless create a filter to remove cells which have any, although in this case it is probably unnecessary.

seurat_CRC1$PROBE_FILTER <- seurat_CRC1$nCount_Unassigned.Codeword == 0 &
                       seurat_CRC1$nCount_Negative.Control.Codeword == 0 &
                       seurat_CRC1$nCount_Negative.Control.Probe == 0
ImageDimPlot(seurat_CRC1, group.by="PROBE_FILTER")

Finally, we can subset the seurat object based on any/all of the filters we have created earlier.

By combining probe, size, and transcript filters, you can retain only the cells that meet all quality criteria, reducing the impact of technical artifacts and noise on your analysis.

seurat_CRC1 <- subset(seurat_CRC1, PROBE_FILTER & SIZE_FILTER_LARGE & SIZE_FILTER_SMALL & TRANSCRIPT_FILTER)
Warning: Not validating Centroids objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating Seurat objectsWarning: Not validating Centroids objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating Seurat objects

Lets examine the cleaned up object - we have lost a few thousand cells from the analysis.

seurat_CRC1
An object of class Seurat 
541 features across 76132 samples within 4 assays 
Active assay: XENIUM (325 features, 0 variable features)
 1 layer present: counts
 3 other assays present: Negative.Control.Codeword, Negative.Control.Probe, Unassigned.Codeword
 2 spatial fields of view present: COLO_CAN CRC1

Data Normalisation

The SCTransform function in Seurat is used for normalizing single-cell RNA-seq and spatial transcriptomics data. This method models the gene expression counts using a regularized negative binomial regression and removes technical noise while preserving biological variability. The clip.range parameter is used to limit the range of the transformed values, which can help stabilize downstream analyses by limiting the influence of extreme values.

seurat_CRC1 <- SCTransform(seurat_CRC1, assay = "XENIUM", clip.range = c(-10, 10))
Running SCTransform on assay: XENIUM
Running SCTransform on layer: counts
vst.flavor='v2' set. Using model with fixed slope and excluding poisson genes.
Variance stabilizing transformation of count matrix of size 325 by 76132
Model formula is y ~ log_umi
Get Negative Binomial regression parameters per gene
Using 321 genes, 5000 cells
Found 60 outliers - those will be ignored in fitting/regularization step

Second step: Get residuals using fitted parameters for 325 genes
Computing corrected count matrix for 325 genes
Calculating gene attributes
Wall clock passed: Time difference of 19.44953 secs
Determine variable features
Centering data matrix

  |                                                                                                
  |                                                                                          |   0%
  |                                                                                                
  |==========================================================================================| 100%
Getting residuals for block 1(of 16) for counts dataset
Getting residuals for block 2(of 16) for counts dataset
Getting residuals for block 3(of 16) for counts dataset
Getting residuals for block 4(of 16) for counts dataset
Getting residuals for block 5(of 16) for counts dataset
Getting residuals for block 6(of 16) for counts dataset
Getting residuals for block 7(of 16) for counts dataset
Getting residuals for block 8(of 16) for counts dataset
Getting residuals for block 9(of 16) for counts dataset
Getting residuals for block 10(of 16) for counts dataset
Getting residuals for block 11(of 16) for counts dataset
Getting residuals for block 12(of 16) for counts dataset
Getting residuals for block 13(of 16) for counts dataset
Getting residuals for block 14(of 16) for counts dataset
Getting residuals for block 15(of 16) for counts dataset
Getting residuals for block 16(of 16) for counts dataset
Centering data matrix

  |                                                                                                
  |                                                                                          |   0%
  |                                                                                                
  |==========================================================================================| 100%
Finished calculating residuals for counts
Set default assay to SCT

Principal Component Analysis (PCA) is a dimensionality reduction technique used to identify the primary axes of variation in high-dimensional data. In the context of spatial transcriptomics, PCA helps to reduce the complexity of the data while preserving the most important patterns of variation.

TIP: If your target panel is very small, you can skip this step and carry out clustering analysis directly on gene expression. This can sometimes help with achieving better clustering results.

seurat_CRC1 <- RunPCA(seurat_CRC1)
PC_ 1 
Positive:  CD24, SLC12A2, RRM2, HMGB2, TYMS, PPP1R1B, CA2, CDCA7, SOX9, FERMT1 
       AQP1, STMN1, PCLAF, C1QBP, EPHB3, CMBL, TK1, MKI67, REG4, CEACAM5 
       EGFR, IMPDH2, TUBA1A, SMOC2, S100P, LGR5, CREB3L1, UBE2C, PTTG1, GATA2 
Negative:  IGFBP7, THBS1, TIMP3, DPYSL3, VCAN, IFITM1, CD79A, MAF, CXCR4, CYBB 
       CTSB, ETS1, IL7R, TRAC, TRBC2, CD3E, APOE, ANXA1, CCL5, CD2 
       SERPINA1, SEC11C, RPS4Y1, SOCS3, PLXND1, SPOCK2, MS4A1, RNASE1, CTLA4, CD8A 
PC_ 2 
Positive:  CTSB, APOE, CYBB, RNASE1, SERPINA1, CD14, MS4A7, C1QC, CD163, C1QA 
       IL7R, C1QB, CCL4, FYB1, GPR183, CCL5, CXCR4, CD8A, TRBC2, CD2 
       CD3E, MAF, CD83, TNFSF13B, GZMA, TRAC, CTLA4, PLXND1, CD3D, TIGIT 
Negative:  THBS1, IGFBP7, TIMP3, VCAN, DPYSL3, CDKN2B, CD79A, SEC11C, FZD7, FKBP11 
       DEPP1, RUNX1T1, AQP1, IFITM1, PRDX4, CD24, CKAP4, FRZB, ALDH1B1, TNFRSF17 
       DERL3, CPE, SELENOM, RRM2, SELENOK, TYMS, SLC12A2, PCLAF, EPHB3, SMOC2 
PC_ 3 
Positive:  TRBC2, TRAC, CD3E, CD2, MS4A1, CXCR4, CD8A, CCL5, SPOCK2, CTLA4 
       GZMK, CD3D, GZMA, CD3G, TIGIT, ETS1, KLRB1, IL7R, CD79A, CD6 
       NKG7, CST7, CD5, TRAT1, BANK1, TRBC1, ITK, LTB, CLU, GIMAP7 
Negative:  CTSB, THBS1, RNASE1, IGFBP7, APOE, CYBB, MS4A7, SERPINA1, CD14, TIMP3 
       C1QC, VCAN, CD163, C1QA, PLXND1, C1QB, FZD7, DPYSL3, ANXA1, CDKN2B 
       SOCS3, RUNX1T1, CPE, TUBA1A, FRZB, AQP1, ALDH1B1, TNFSF13B, DEPP1, IL4I1 
PC_ 4 
Positive:  THBS1, CD8A, CCL5, TIMP3, CD2, GZMA, IGFBP7, TRBC2, CD3E, CTLA4 
       TRAC, VCAN, CD3G, CD3D, CCL4, SPOCK2, TIGIT, NKG7, ETS1, KLRB1 
       IL7R, CD6, GZMK, CST7, MAF, AQP1, FOXP3, GNLY, ICOS, CD24 
Negative:  CD79A, SEC11C, FKBP11, MS4A1, TNFRSF17, DERL3, PRDX4, RGS13, SELENOK, CXCR4 
       LRMP, BANK1, CLU, CD79B, SPIB, FCRL1, C2orf88, CYBB, CKAP4, SMIM14 
       GPR183, IRF8, PAX5, CD14, APOE, SELL, TCL1A, MS4A7, RPS4Y1, CD83 
PC_ 5 
Positive:  MS4A1, CLU, BANK1, CXCR4, IGFBP7, SPIB, AQP1, PLXND1, ETS1, DEPP1 
       SOCS3, SMIM14, FCRL1, IFITM1, CPE, IRF8, SELL, ROBO1, LRMP, ARHGAP24 
       IL7R, RPS4Y1, CD83, PAX5, CCR7, TCL1A, LTB, TNFRSF25, MAF, FCER2 
Negative:  SEC11C, CCL5, CD8A, FKBP11, THBS1, GZMA, CD79A, PRDX4, DERL3, TNFRSF17 
       CCL4, NKG7, SELENOK, CD2, CD3E, GNLY, VCAN, CD3G, CKAP4, CD8B 
       CD3D, GZMK, KLRD1, KLRC2, CST7, TRAC, C2orf88, CTSB, CDKN2B, TRBC2 

As before, we can visualise how much variation is captured by each PC.

The ElbowPlot function helps to determine the number of significant PCs to use for downstream analyses. The plot typically shows the amount of variance explained by each PC, and the “elbow” point indicates a natural cutoff.

ElbowPlot(seurat_CRC1, 50)

Plotting the top genes contributing to a specific principal component helps in understanding the biological factors driving the variation captured by that component. This type of plot highlights the genes with the highest loadings, which are the most influential in the principal component analysis.

PC_Plotting(seurat_CRC1, dim_number = 1)

The FeaturePlot function in Seurat is used to visualize the expression of a specific gene across cells in a given dimensionality reduction space (e.g., PCA). This helps to understand how the expression of a gene varies across the principal components.

FeaturePlot(seurat_CRC1, "CEACAM5", reduction = "pca") + scale_color_viridis_c()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

We can also examine how various PCs are distributed spatially.

Here, we can see that high PC1 loadings enrich in follicular structures and low PC1 loadings enrich in crypt top cells.

ImageFeaturePlot(seurat_CRC1, "PC_1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

We can plot the expression of high (or low) loading genes to visualise how this correlates with our dimensionality reduction.

ImageFeaturePlot(seurat_CRC1, "SMOC2", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Next, we will use the reduced dimensionality data for clustering and cluster visualisation.

RunUMAP: Perform Uniform Manifold Approximation and Projection (UMAP) to reduce the dimensionality of the data for visualization. The UMAP plot reduces the high-dimensional data to two dimensions, preserving the local and global structure of the data for visualization. Cells that are close together in the UMAP plot are similar in their gene expression profiles. seurat: The Seurat object. dims = 1:20: Specifies the principal components to use for UMAP.

FindNeighbors: Finding nearest neighbors helps to identify cells that are similar based on their PCA scores, which is used for clustering. seurat: The Seurat object. reduction = “pca”: Specifies that the PCA space should be used for finding neighbors. dims = 1:20: Specifies the principal components to use for identifying neighbors.

FindClusters: Clustering identifies distinct groups of cells with similar gene expression patterns. The resolution parameter controls the granularity of the clustering. seurat: The Seurat object. resolution = 0.7: Sets the resolution parameter for clustering. Higher values lead to more clusters, while lower values lead to fewer clusters.

seurat_CRC1 <- RunUMAP(seurat_CRC1, dims = 1:20)
10:58:18 UMAP embedding parameters a = 0.9922 b = 1.112
Found more than one class "dist" in cache; using the first, from namespace 'spam'
Also defined by ‘BiocGenerics’
10:58:18 Read 76132 rows and found 20 numeric columns
10:58:18 Using Annoy for neighbor search, n_neighbors = 30
Found more than one class "dist" in cache; using the first, from namespace 'spam'
Also defined by ‘BiocGenerics’
10:58:18 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:58:32 Writing NN index file to temp file /project/.tmpRsessions/Rtmpyt3o6r/file1d417c7f871a50
10:58:32 Searching Annoy index using 1 thread, search_k = 3000
10:59:24 Annoy recall = 100%
10:59:34 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
10:59:49 Initializing from normalized Laplacian + noise (using RSpectra)
10:59:52 Commencing optimization for 200 epochs, with 3497926 positive edges
Using method 'umap'
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:00:44 Optimization finished
seurat_CRC1 <- FindNeighbors(seurat_CRC1, reduction = "pca", dims = 1:20)
Computing nearest neighbor graph
Computing SNN
seurat_CRC1 <- FindClusters(seurat_CRC1, resolution = 0.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 76132
Number of edges: 2461396

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9078
Number of communities: 5
Elapsed time: 67 seconds

Next lets visualise the clusters - firstly, based on transcriptome embedding.

DimPlot: Creates a scatter plot of cells in a reduced-dimensional space, by default now using UMAP dimensionality reduction. seurat: The Seurat object containing the dimensionality reduction results and cluster assignments. label = TRUE: Adds cluster labels to the plot. repel = TRUE: Repels the labels to avoid overlapping, making the plot clearer.

DimPlot(seurat_CRC1, label=T, repel=T)

And now lets plot the clusters in tissue space.

We can see that our clusters have quite nice correspondence to distinct spatial regions.


ImageDimPlot(seurat_CRC1, size=.5, axes=T)
Warning: No FOV associated with assay 'SCT', using global default FOV

As before, now we can use Seurat differential expression functions to identify marker genes for specific cell clusters.

FindMarkers: Identifies genes that are differentially expressed in a specified cluster compared to all other cells. seurat: The Seurat object containing the gene expression data and cluster identities. ident.1 = “0”: Specifies the cluster of interest for which marker genes are to be identified. In this case, cluster “0”. max.cells.per.ident = 500: Limits the number of cells to be used from each cluster for the differential expression analysis to 500. This can help to speed up the computation.

markers <- FindMarkers(seurat_CRC1, ident.1="0", max.cells.per.ident=500)
head(markers)

We can visualise expression of cluster specific markers using feature plots

FeaturePlot(seurat_CRC1, "CD3E", label=T, repel=T)+ scale_color_viridis_c(direction=-1)
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

FeaturePlot(seurat_CRC1, "MS4A1", label=T, repel=T)+  scale_color_viridis_c(direction=-1)
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

FeaturePlot(seurat_CRC1, "CEACAM5", label=T, repel=T)+ scale_color_viridis_c(direction=-1)
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

FeaturePlot(seurat_CRC1, "KIT", label=T, repel=T)+ scale_color_viridis_c(direction=-1)
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

FeaturePlot(seurat_CRC1, "CD24", label=T, repel=T)+ scale_color_viridis_c(direction=-1)
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

Or, as in our sequencing ST tutorial, detect and visualise top markers for every cluster.

markers <- FindAllMarkers(seurat_CRC1, max.cells.per.ident = 500)
Calculating cluster 0
Calculating cluster 1
Calculating cluster 2
Calculating cluster 3
Calculating cluster 4
seurat_CRC1
An object of class Seurat 
877 features across 76132 samples within 6 assays 
Active assay: SCT (325 features, 325 variable features)
 3 layers present: counts, data, scale.data
 5 other assays present: XENIUM, Negative.Control.Codeword, Negative.Control.Probe, Unassigned.Codeword, prediction.score.id
 2 dimensional reductions calculated: pca, umap
 2 spatial fields of view present: COLO_CAN CRC1

scCustomize package provides a convenient helper function, Extract_Top_Markers, to extract the top marker genes for each cluster from the output of FindAllMarkers. This function simplifies the process of identifying and retrieving the most significant marker genes for analysis and visualisation.

In this case, we are extracting the top five markers per cluster.

top <- Extract_Top_Markers(markers, num_genes = 5, named_vector = FALSE, make_unique = TRUE)
top
 [1] "SCNN1A"   "LGR5"     "DMBT1"    "EPHB3"    "IL17RB"   "DERL3"    "THBS1"    "TNFRSF17"
 [9] "CPE"      "VCAN"     "MS4A1"    "CTLA4"    "TRBC2"    "CD3D"     "GZMA"     "APOE"    
[17] "RNASE1"   "C1QC"     "C1QB"     "CD163"    "CPA3"     "SLC18A2"  "IL1RL1"   "MS4A2"   
[25] "KIT"     

Clustered_DotPlot function from the scCustomize package provides a convenient and visually appealing way to display expression patterns of top marker genes across clusters using a dot plot. This function not only plots the expression data but also clusters the genes and groups for enhanced visual interpretation. This is an alternative to Seurat DotPlot function.

k = 18: Determines the number of clusters for the hierarchical clustering of genes to enhance visual separation of expression patterns.

We can see that most clusters have unique markers, which suggests the dataset is not over-clustered.

Clustered_DotPlot(seurat_CRC1, features = top, k=18, group.by = "predicted.id")
[[1]]

[[2]]

Additional Spatial Visualisations

The resolution of in situ datasets is typically very high and so it can be difficult to visualise everything in one plot. Below, we will explore different visualisations that can help unpick and understand the data a bit better.

To better visualise spatial distribution of clusters, sometimes it can be useful to subset only certain groups to reduce crowding. Here, we specifically only visualising two selected clusters.

WhichCells: Identifies cells based on specified criteria. seurat: The Seurat object. expression = seurat_clusters %in% c(0, 5): Logical expression to select cells belonging to clusters 0 and 5.

This works with ImageFeaturePlot too. Try it with some genes!

ImageDimPlot(seurat_CRC1, cells=WhichCells(seurat_CRC1, expression = seurat_clusters %in% c(0, 5)))
Warning: No FOV associated with assay 'SCT', using global default FOV

Sometimes, it can be useful to create additional fields of view of the data - for example, zooms of specific regions. First, let’s look at the coordinate system by plotting the data and turning on the plotting of the axes, which are off by default to create nicer looking plots.

This gives us a rough idea on where in the coordinate system to create any subsets or zooms of the data.

For example, if we want to zoom in on the follicle in the top right corner, we can see that it lies roughly between 4000-5000 and 8000-9000 coordinate regions.

ImageDimPlot(seurat_CRC1, axes = T)
Warning: No FOV associated with assay 'SCT', using global default FOV

Cell Type Identification

You can manually annotate your cell clusters, or you can classify them using a reference single-cell dataset. This process is simpler than for Visium data because our data is at the single-cell level, establishing a one-to-one relationship without the need for spot deconvolution.

However, our transcriptome is more limited here, and some cell types may not be well represented. Additionally, our single-cell reference might be missing some cell types that are not well captured by droplet-based technologies but are present in our tissue data.

In this example, we will use a single-cell reference dataset that we prepared earlier.

We will start by reading in the seurat RDS file.

ref <- readRDS("/project/shared/spatial_data_camp/datasets/SINGLE_CELL_REFERENCES/COLON_HC_5K_CELLS.RDS")

Examine the object:

ref
An object of class Seurat 
33556 features across 5725 samples within 3 assays 
Active assay: RNA (33538 features, 2000 variable features)
 3 layers present: counts, data, scale.data
 2 other assays present: HTO, ADT
 2 dimensional reductions calculated: pca, umap

And plot the pre-computed cell clusters. We can see that here we have quite high level annotation.

DimPlot(ref)

We want to evaluate how much structural information is lost in single-cell data when limiting ourselves to the targeted gene set. Accurate cluster prediction is challenging if the current gene set does not adequately identify them. To do this, we will quickly re-embedd the data using only the genes present in our spatial transcriptomics data and keep the original cluster annotations derived from unbiased data.

In this example, we can observe that the limited gene set does a reasonably good job at distinguishing major cell populations. However, it struggles to differentiate between similar cell types, such as myofibroblasts and fibroblasts, as effectively as before.

ref <- SCTransform(ref, residual.features =rownames(seurat))
Running SCTransform on assay: RNA
Computing residuals for the 325 specified features
vst.flavor='v2' set. Using model with fixed slope and excluding poisson genes.
Calculating cell attributes from input UMI matrix: log_umi
Variance stabilizing transformation of count matrix of size 19008 by 5725
Model formula is y ~ log_umi
Get Negative Binomial regression parameters per gene
Using 2000 genes, 5000 cells
Found 80 outliers - those will be ignored in fitting/regularization step

Skip calculation of full residual matrix
Calculating gene attributes
Wall clock passed: Time difference of 28.10518 secs
Determine variable features
Setting min_variance based on median UMI:  0.16
Calculating residuals of type pearson for 319 genes

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |==================================                                  |  50%
  |                                                                          
  |====================================================================| 100%
Centering data matrix

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
Place corrected count matrix in counts slot
Set default assay to SCT
ref <- RunPCA(ref)
PC_ 1 
Positive:  IGFBP7, SOCS3, APOE, IFITM1, TUBA1A, TIMP3, ANXA1, SELENOM, FRZB, VCAN 
       DEPP1, TNFAIP3, THBS1, CXCR4, CCL5, IRF8, RARRES2, CLU, GIMAP7, CD83 
       CD3E, TUBB, GPR183, CCL4, IL7R, PTGER4, ETS1, CD7, RORA, DPYSL3 
Negative:  GUCA2A, SLC26A3, CEACAM7, CA1, CA2, SLC26A2, CEACAM1, AQP8, CEACAM5, CDHR5 
       MS4A12, CA4, SDCBP2, GUCA2B, SELENBP1, MYH14, CD24, MUC12, CD177, CLCA4 
       TMIGD1, HRCT1, CEACAM6, AREG, CES2, C2orf88, GNA11, IL32, SLC6A8, CDKN2B 
PC_ 2 
Positive:  IGFBP7, SOCS3, APOE, GUCA2A, IFITM1, TIMP3, CEACAM7, SLC26A3, TUBA1A, CA4 
       AQP8, CEACAM1, SELENOM, MS4A12, GUCA2B, VCAN, THBS1, DEPP1, CEACAM5, SLC26A2 
       IL32, FRZB, CDHR5, TMIGD1, CLCA4, CD177, SDCBP2, HRCT1, CA1, ANXA1 
Negative:  CXCL3, ADH1C, WFDC2, PPP1R1B, LEFTY1, CCL5, DERL3, CD24, STMN1, UBE2C 
       CD79A, C1QBP, PTTG1, KRTCAP3, CCL20, AREG, TYMS, SLPI, SEC11C, OLFM4 
       CD3E, LGALS2, HMGB2, CDCA7, RHOV, CXCR4, SOX9, GPR183, TKT, LCN2 
PC_ 3 
Positive:  CXCL3, ADH1C, APOE, WFDC2, CD24, SOCS3, IGFBP7, RARRES2, PPP1R1B, LEFTY1 
       SLPI, SELENBP1, KRTCAP3, UBE2C, C1QBP, STMN1, LGALS2, UGT2B17, CA2, LCN2 
       AREG, THBS1, OLFM4, TIMP3, TYMS, NXPE4, CES2, SOX9, CMBL, RHOV 
Negative:  CCL5, CD3E, ANXA1, CXCR4, IL7R, IL32, TNFAIP3, CD7, GPR183, KLRB1 
       CST7, LTB, CD3D, FYB1, CD2, NKG7, LEPROTL1, CCL4, SPOCK2, TRBC2 
       TRBC1, CD8A, CD83, PTGER4, XCL2, CCR7, RORA, CEACAM7, SELENOK, AQP8 
PC_ 4 
Positive:  DERL3, CD79A, TNFRSF17, FKBP11, CCL4, SEC11C, CD83, CA4, AQP8, MS4A1 
       VPREB3, CD79B, GUCA2B, CEACAM7, PRDX4, GUCA2A, CEACAM1, C1QA, C1QC, C1QB 
       DNASE1L3, FCRLA, SELENOM, BANK1, MS4A7, CLCA4, FCER2, LRMP, IL1B, TCL1A 
Negative:  CXCL3, ANXA1, CD3E, ADH1C, CD24, IL7R, WFDC2, CA1, IL32, CA2 
       APOE, CD7, SELENBP1, KLRB1, AREG, PPP1R1B, CCL20, TNFAIP3, RARRES2, SLPI 
       SOCS3, CCL5, LEPROTL1, THBS1, LEFTY1, IFITM1, LGALS2, CD3D, UBE2C, STMN1 
PC_ 5 
Positive:  APOE, CA1, CA2, SLC26A2, SELENBP1, THBS1, SLC26A3, CCL4, VCAN, CD24 
       DERL3, TNFAIP3, IRF8, MUC12, SOCS3, GPR183, CCL5, CD79A, CDHR5, CES2 
       LGALS2, C1QA, TNFRSF17, C1QC, CD14, C1QB, AREG, UGT2B17, ADH1C, FKBP11 
Negative:  CA4, IGFBP7, CLU, RNASE1, GIMAP7, TUBA1A, CA7, BEST4, STMN1, SPIB 
       GUCA2B, WFDC2, OTOP2, HEPACAM2, GUCA2A, AQP1, AQP8, ANXA1, TIMP3, IL32 
       CEACAM6, UBE2C, IFITM1, CEACAM1, KLK1, HES6, RETNLB, TUBB, CLCA4, CTSE 
ref <- RunUMAP(ref, dims=1:20)
14:35:49 UMAP embedding parameters a = 0.9922 b = 1.112
Found more than one class "dist" in cache; using the first, from namespace 'spam'
Also defined by ‘BiocGenerics’
14:35:49 Read 5725 rows and found 20 numeric columns
14:35:49 Using Annoy for neighbor search, n_neighbors = 30
Found more than one class "dist" in cache; using the first, from namespace 'spam'
Also defined by ‘BiocGenerics’
14:35:49 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
14:35:50 Writing NN index file to temp file /project/.tmpRsessions/RtmpONHL0d/file197e2b5e6157d0
14:35:50 Searching Annoy index using 1 thread, search_k = 3000
14:35:52 Annoy recall = 100%
14:36:01 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
14:36:18 Initializing from normalized Laplacian + noise (using RSpectra)
14:36:18 Commencing optimization for 500 epochs, with 231718 positive edges
Using method 'umap'
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
14:36:35 Optimization finished
DimPlot(ref, label=T, repel=T)

If we visualise the specificity of the gene panel across our single cell reference clusters, we can see that the panel coverage is mainly concentrated across epithelial cells and T-Cells and other immune cells, with few specific markers expressed by stromal cells.

ps <- AggregateExpression(ref, features = rownames(seurat), normalization.method = "LogNormalize", assays="RNA", return.seurat = T)

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |======                                                              |   9%
  |                                                                          
  |============                                                        |  18%
  |                                                                          
  |===================                                                 |  27%
  |                                                                          
  |=========================                                           |  36%
  |                                                                          
  |===============================                                     |  45%
  |                                                                          
  |=====================================                               |  55%
  |                                                                          
  |===========================================                         |  64%
  |                                                                          
  |=================================================                   |  73%
  |                                                                          
  |========================================================            |  82%
  |                                                                          
  |==============================================================      |  91%
  |                                                                          
  |====================================================================| 100%
ps <- ScaleData(ps, features=rownames(ps))
Centering and scaling data matrix

  |                                                                          
  |                                                                    |   0%
  |                                                                          
  |====================================================================| 100%
pheatmap(LayerData(ps, layer="scale.data"), show_rownames = F)

Next, we can use the standard Seurat integration and cross-classification workflow to transfer single-cell derived labels to our spatial object.

Briefly, the first function identifies anchors between the reference single-cell dataset (ref) and the query spatial dataset (seurat). Anchors are pairs of cells that are considered similar between the datasets. The normalization.method = “SCT” specifies that SCTransform normalization should be used.

The second step transfers the cell type labels from the reference dataset to the query dataset. The anchorset argument specifies the anchors found in the previous step. The refdata = ref$CellType argument specifies the cell type labels from the reference dataset to be transferred. The prediction.assay = TRUE argument indicates that the transferred labels should be stored in a new assay in the query dataset. The weight.reduction = seurat[[“pca”]] argument specifies the dimensionality reduction to be used for weighting the transfer, and dims = 1:30 specifies the number of dimensions to use.

anchors <- FindTransferAnchors(reference = ref, 
                               query = seurat_CRC1, 
                               normalization.method = "SCT")
Normalizing query using reference SCT model
Warning: No layers found matching search pattern providedPerforming PCA on the provided reference using 319 features as input.
Projecting cell embeddings
Finding neighborhoods
Finding anchors
    Found 4981 anchors
seurat_CRC1 <- TransferData(anchorset = anchors, 
                       refdata = ref$CellType, 
                       prediction.assay = TRUE,
                       weight.reduction = seurat_CRC1[["pca"]], 
                       query = seurat_CRC1, 
                       dims=1:30)
Finding integration vectors
Finding integration vector weights
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Predicting cell labels
Warning: Layer counts isn't present in the assay object; returning NULL

Unfortunately, the predicted labels and spatial clusters do not correspond clearly in all cases. This discrepancy is particularly evident in the middle regions of the UMAP, where many cells are predicted as epithelial cells - probably incorrectly!

How to improve this?

Ensure Good Representation of Cell Type Markers in in situ Target Panel Most critically, before undertaking any experiments you want to ensure that there is good representation of all cell types in your target panel - in this case, there is not much to be done as the data has already been generated.

Review and Refine Reference Data: Ensure that the reference single-cell dataset is comprehensive and accurately annotated. If certain cell types are not well represented or annotated in the reference dataset, it can lead to misclassification.

Increase the Number of Dimensions: Increasing the number of dimensions used in the UMAP and PCA steps might capture more variance in the data, leading to better label transfer.

Filter and Preprocess Data: Filtering out low-quality cells or genes and performing additional preprocessing steps can enhance the accuracy of the transfer anchors and, consequently, the label predictions.

Manually Annotate or Correct Predictions: In cases where automatic label transfer is insufficient, consider manually annotating or correcting the predictions for critical regions to ensure accuracy.

As before, we can also visualise the predicted cell labels in tissue space.

ImageDimPlot(seurat_CRC1, cols = cell_colours, group.by = "predicted.id")
Warning: No FOV associated with assay 'SCT', using global default FOV

In line with non-specific predictions, we can also see that the prediction score across these areas is lower.

Outside of stromal cells, we can also see that prediction probability can be low in cells that embedd “between” clusters, for example between core T-Cells and B-Cells, two populations that should be distinct.

This is often the case where cell segmentation is imperfect and partitions transcripts in such a way that it generates “artificial” doublets by pulling in transcripts from an adjacent cell.

FeaturePlot(seurat_CRC1, "predicted.id.score")

For example, if we visualise the lineage markers for T-Cells and B-Cells, we can see that they are often “co-expressed” in the same cells when biologically, they should not be.

The FeatureScatter function in Seurat is used to create a scatter plot showing the relationship between the expression levels of two genes across all cells. This visualization helps to identify potential correlations or patterns between the two genes.

#for the sake of the tutorial we skipped segmentaiton, moved streight to nearest neighbour.

To improve these artefacts, we can try alternative cell segmentation algorithms. What works best is very tissue dependant and there’s no easy one stop solution to this. Cell segmentation algorithms can be divided into a few groups.

Nuclei-based Segmentation algorithms primarily focus on identifying cell nuclei, which are usually more distinct and easier to detect than the cell boundaries. Once the nuclei are identified, the cell boundaries are inferred by expanding around the nuclei. This approach works well in tissues where the nuclei are clearly visible and distinct and in early versions of many in situ platforms, were the only available methods due to only using DAPI stain.

Cell Boundary-Based Segmentation algorithms (e.g. Cellpose) directly segments cells by identifying their boundaries. It is particularly effective for images with complex cell shapes and varying sizes, but this required good cell boundary staining - this is not available for our test dataset. Often cell boundary staining can be non-uniform across different tissues, adding further difficulties. Cellpose version 3 incorporates user-guided model training, which can be very useful for difficult to segment cell types - but this requires time investment to annotate training examples.

Transcript-Density Based Segmentation algorithms, like Baysor segments cells based on the spatial distribution of transcripts. It uses Bayesian inference to assign transcripts to cells, considering both the density and distribution of RNA molecules. This can be very useful for improving cell segmentation where cell boundary stain is not available or not working well.

In this case, we will try re-segmenting our data with Baysor. Here’s the run we prepared earlier - see supplementary material on how to process the data yourself.

baysor <- "/project/shared/spatial_data_camp/datasets/PRECOMPUTED/baysor"

The key output of baysor is the file with transcripts, which have been re-assigned to a new cell identifier.

seg <- read_csv(file.path(baysor, "segmentation.csv"))

head(seg)
#
There will be some transcripts that cannot be assigned to a cell - about 10% in this case. This information is stored under "is_noise" flag. 
This is fairly normal levels of noise.

<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxudGFibGUoc2VnJGlzX25vaXNlKVxuYGBgIn0= -->

```r
table(seg$is_noise)
qplot(seg$assignment_confidence)
table(seg$assignment_confidence > .9)

And transcript confidence - the confidence that the molecule itself is real and not noise.

qplot(seg$confidence)
table(seg$confidence > .9)

We can filter out low confidence and low assignment confidence transcripts here from further analysis. How stringent you want to be depends on whether you want to keep as much data as possible and accept some inaccuracies, or end up with the cleanest possible dataset.

Here, we will filter out transcripts that have not been assigned to cells, and below 0.9 confidence and assignment confidence.

Then, we tabulate a cell by gene matrix from these data.

filtered <- seg[seg$confidence > .9 & seg$assignment_confidence > .9 & !seg$is_noise, ]
mat <- table(filtered$gene, filtered$cell)
mat <- matrix(mat, ncol = ncol(mat), dimnames = dimnames(mat))

Baysor further provides diagnostic info about cells in “segmentation_cell_stats.csv” file, which we will also read in here. The following parameters can be used to filter low-quality cells:

area: area of the convex hull around the cell molecules avg_confidence: average confidence of the cell molecules density: the number of molecules in a cell divided by the cell area elongation: ratio of the two eigenvalues of the cell covariance matrix n_transcripts: number of molecules per cell avg_assignment_confidence: average assignment confidence per cell. Cells with low avg_assignment_confidence have a much higher chance of being an artifact. max_cluster_frac (only if n-clusters > 1): fraction of the molecules coming from the most popular cluster. Cells with low max_cluster_frac are often doublets. lifespan: number of iterations the given component exists. The maximal lifespan is clipped proportionally to the total number of iterations. Components with a short lifespan likely correspond to noise.

stats <- read_csv(file.path(baysor, "segmentation_cell_stats.csv"))
stats <- as.data.frame(stats)
rownames(stats) <- stats$cell
head(stats)

Now, we can assemble a seurat object as before - we first construct a basic object with cell by gene matrix and cell meta data.

seurat_reseg <- CreateSeuratObject(counts = mat, assay = "XENIUM", meta.data = as.data.frame(stats))

Different segmentation algorithms output cell boundaries in various cell formats.

GeoJSON is a format for encoding various geographic data structures using JavaScript Object Notation (JSON). It is widely used for representing spatial features and their attributes and is used by some algorithms to store and output cell segmentation boundaries.

In R, we can read in polygon data from a GeoJSON file using the FROM_GeoJson function.

NOTE: baysor is a 3D cell segmentation algorithm. This means it considered z-stack information. Some algorithms only perform cell segmentation on a representative layer - e.g. Merscope Cellpose takes the middle z-stack. This resegmented data represents 3D segmentations of cells. Since these are 3D segmentations, they may look unusual when visualized as 2D projections.

polygons <- FROM_GeoJson(file.path(baysor, "segmentation_polygons.json"))

In the below code, we extract the polygon coordinates from the data and reformat them into a data frame that Seurat requires to construct a Segmentation object.

polygons <- lapply(1:length(polygons$geometries), FUN=function(x){
  df <- as.data.frame(polygons$geometries[[x]]$coordinates)
  df$cell_id <- paste0("CRef9694c57-", x)
  df
  })

polygons <- do.call(rbind, polygons)
colnames(polygons) <- c("x", "y", "cell_id")
polygons <- polygons[polygons$cell_id %in% Cells(seurat_reseg), ]
polygons <- CreateSegmentation(polygons)

Then, as before, we add both the cell centroid and cell boundaries as segmentations to the seurat object. We skip adding individual molecule coordinates for now.

cents <- CreateCentroids(stats[Cells(seurat_reseg), c("x", "y")])
cents@cells <- Cells(seurat_reseg)
coords <- CreateFOV(coords =list(centroids = cents, segmentation=polygons) ,
                    type = c("centroids", "segmentation"), 
                    molecules = NULL,
                    assay = "XENIUM")

seurat_reseg[["COLON"]] <- coords

From here, we can use the seurat object to visualise various cell meta data - for example, average transcript assignment confidence per cell.

ImageFeaturePlot(seurat_reseg, "avg_assignment_confidence" ) + scale_fill_viridis_c()

Lets filter out low count cells and re-cluster the data as before

seurat_reseg$FILT <- seurat_reseg$nCount_XENIUM >= 15
seurat_reseg <- subset(seurat_reseg, FILT)
seurat_reseg <- SCTransform(seurat_reseg, assay = "XENIUM", clip.range = c(-10, 10))
seurat_reseg <- RunPCA(seurat_reseg)
seurat_reseg <- RunUMAP(seurat_reseg, dims = 1:20)
seurat_reseg <- FindNeighbors(seurat_reseg, reduction = "pca", dims = 1:20)
seurat_reseg <- FindClusters(seurat_reseg, resolution = 0.3)

Visualising clusters, we can see that we already obtain a better separation in the UMAP embedding than before. Though of course, distances in the UMAP space can be very misleading and careful interpretation is required.

DimPlot(seurat_reseg, label=T, repel = T)

Next we visualise the clusters in tissue space.

ImageDimPlot(seurat_reseg)

As before, lets cross-classify our cells using the reference single cell dataset

anchors <- FindTransferAnchors(reference = ref, query = seurat_reseg, normalization.method = "SCT")

seurat_reseg <- TransferData(anchorset = anchors, refdata = ref$CellType, prediction.assay = TRUE,
    weight.reduction = seurat_reseg[["pca"]], query = seurat_reseg, dims=1:30)

Visualising the predictions, we’ve separated T-Cells from B-Cells much better. The stromal clusters still predict poorly, but that is due to poor probe coverage.

DimPlot(seurat_reseg, group.by = "predicted.id")

We can check the distribution in tissue space:

ImageDimPlot(seurat_reseg, group.by = "predicted.id")
FeaturePlot(seurat_reseg,"predicted.id.score")

#skipped segmentaiton, moved streight to nearest neighbour.

Spatial Neighbourhood Analyis

Spatial neighbourhood analysis identifies cells that are spatially close to each other within a tissue section. This technique helps to understand the spatial organization and potential interactions between cells. The same principles used in Visium data can be applied to in situ data.

GetTissueCoordinates: Retrieves the spatial coordinates of the centroids from the Seurat object. which = “centroids”: Specifies that the centroids’ coordinates should be retrieved. rownames(coords) <- coords$cell: Sets the row names of the coords data frame to the cell IDs.

FindNeighbors: Identifies the nearest neighbours for each cell based on their spatial coordinates. as.matrix(coords[, c(“x”, “y”)]): Converts the x and y coordinates to a matrix format. k.param = 20: Specifies the number of nearest neighbours to identify for each cell. return.neighbor = TRUE: Ensures that the function returns the neighbour indices and distances.

TIP: This approach identifies spatial neighbours. If you analysis requires precise identification of directly adjacent or interacting cell neighbours, then a delaunay network based approach would be more appropriate. R package Giotto implements some nice functionalities based on this

coords <- GetTissueCoordinates(seurat_CRC1, which = "centroids")
rownames(coords) <- coords$cell
neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 20, return.neighbor=TRUE)

As in our Visium example, we can use it automatically select cells that are adjacent or physically close to some feature of interest. In this case, we want to automatically select any cells that are nearby the group of cells which have been designated as cluster 8, which are mid-crypt epithelial cells.

This can be useful to explore how cells in adjacent cells might influence or interact with each other.

WhichCells identifies the cells that belong to a specific cluster or meet a particular expression criterion

TopNeighbors finds the top n nearest neighbours for a given set of cells based on the k-NN graph. Increasing the n here will return more and more distal spots - tweak to your requirements!

How would you analyse these groups further? What are the adjacent cells? Are they heterogenous? What do they express?

cells <- WhichCells(seurat_CRC1, expression= SCT_snn_res.0.7 == 8)
adjacent <- TopNeighbors(neighbours, cells, n = 10)

Idents(seurat_CRC1) <- "Other Cells"
seurat_CRC1 <- SetIdent(seurat_CRC1, cells = adjacent, "Adjacent Cells")
seurat_CRC1 <- SetIdent(seurat_CRC1, cells = cells, "Cells of Interest")

ImageDimPlot(seurat_CRC1)

seurat_CRC1[["group1"]] <- Idents(seurat_CRC1)

Finding Spatially Correlated Genes

We can use the spatial neighbourhood graph to identify spatially correlated features.

We start by calculating, for each cell, the pseudobulk expression of all cells in it’s local neighbourhood. First of all, lets expand the k.param to include more neighbours - this will control whether we’re including more distal gene expression.

FindNeighbors: Identifies the nearest neighbours for each cell based on their spatial coordinates. as.matrix(coords[, c(“x”, “y”)]): Converts the x and y coordinates to a matrix format. k.param = 50: Specifies the number of nearest neighbours to identify for each cell.

LayerData: Extracts the gene expression data from the specified layer and assay in the Seurat object. layer = “counts”: Specifies that the counts layer should be extracted. assay = “XENIUM”: Specifies the assay from which to extract the data.

as.matrix(neighbours$nn %*% t(mt)): Multiplies the neighbour matrix with the transpose of the gene expression matrix to aggregate the expression levels of neighbouring cells.

neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 50)
mt <- LayerData(seurat_CRC1, layer = "counts", assay = "XENIUM")
sum_mtx <- as.matrix(neighbours$nn %*% t(mt))

We can store the neighbourhood-aggregated values in our Seurat object as a separate assay, which we will call “NEIGHBOURHOOD50”. We then normalise the matrix.

seurat_CRC1[["NEIGHBOURHOOD50"]] <- CreateAssayObject(t(sum_mtx))
seurat_CRC1 <- NormalizeData(seurat_CRC1, assay = "NEIGHBOURHOOD50")

We can then apply quick correlation calculations to identify spatially correlated features.

corrgenes <- cor(as.matrix(t(LayerData(seurat_CRC1, assay = "NEIGHBOURHOOD50", layer = "data"))))
diag(corrgenes) <- 0
high_corr_genes <- which(rowMaxs(corrgenes) > .7)
diag(corrgenes) <- 1
heatmap <- pheatmap(corrgenes[high_corr_genes, high_corr_genes], border_color = NA)

Here, we use a very quick cutree function to partition the hierachical clustering into 5 groups of co-localising genes.

TIP- for more advanced module detection, you can use the spatial neighbourhood aggregated matrix with tools such as WGCNA (though it has its own meta-cell functionality).

modules <- cutree(heatmap$tree_row, 5)
modules

Lets visualize some of the detected spatially co-localizing genes. For example, module 1 genes - we can see that CD24 and SOX9 are spatially similar, but not necessarily always expressed by the same cells.


ImageFeaturePlot(seurat_CRC1, "CD24") + scale_fill_viridis_c()
ImageFeaturePlot(seurat_CRC1, "ANXA1") + scale_fill_viridis_c()

#CD24 mosty cancer cells 
# ANXA1 produced by macrophages, neutrophils and epithelial cells 
FeaturePlot(seurat_CRC1, c("ANXA1", "CD24"))

Similarly, module 4 genes which are expressed by different cell types, but show strong co-localisation:

ImageFeaturePlot(seurat_CRC1, "APOE") + scale_fill_viridis_c()
ImageFeaturePlot(seurat_CRC1, "MS4A7") + scale_fill_viridis_c()

We can use Seurat’s module score functionality to add the modules to the meta data as an aggregate score for ease of visualisation.

The AddModuleScore function in Seurat is used to calculate and add scores for predefined gene modules to a Seurat object. These module scores represent the average expression levels of a set of genes (modules) for each cell, which can be used to identify specific biological processes or cell states.

seurat: The Seurat object to which the module scores are added. features = split(names(modules), modules): Specifies the gene modules. The split function is used to create a list of gene sets, where each set corresponds to a specific module. assay = “SCT”: Specifies the assay from which to calculate the module scores. Here, “SCT” refers to the SCTransform-normalized data. nbin = 3: Divides the genes into 3 bins based on their average expression levels to account for differences in gene expression distributions. Need to reduce this from defaults as our number of measured genes is much lower than in single cell analysis. name = “MOD”: Specifies the prefix for the module score names. The resulting scores will be named “MOD1”, “MOD2”, etc.

seurat <- AddModuleScore(seurat, features=split(names(modules), modules), assay = "SCT", nbin=3, name = "MOD" )

Visualising module scores - we can see that we have identified a group of genes co-localising at the base of the epithelial crypts (MOD1) and another module of genes co-localising in lymphoid follicles.

ImageFeaturePlot(seurat, "MOD1") + scale_fill_viridis_c()
ImageFeaturePlot(seurat, "MOD4") + scale_fill_viridis_c()

Detecting Cellular Niches

We can use a similar approach to define cellular niches.

Lets repeat the process of defining spatial neighbourhood expression, with the following modifications:

  1. Expand the nearest neighbours even further, to consider 100 nearby cells.

  2. Exclude the transcriptomes of the cells themselves and only consider the expression of nearby cells.

This approach will enable us to group cells based not on their identity, but on their microenvironment.

neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 100)
diag(neighbours$nn) <- 0 # dont count transcriptome of the cell itself, just neighbours
mt <- LayerData(seurat, layer = "counts", assay = "XENIUM")
sum_mtx <- as.matrix(neighbours$nn %*% t(mt))

How is this useful? Well, now you can cluster cells not on their gene expression values, but gene expression values of surrounding cells. This effectively partitions cells not based on their identity, but on their micro-environment! Using this approach, you can identify tissue niches

Alternative approaches - you could count cell types rather than gene expression values, but that requires you to have finalised cell annotation for your dataset, which is not ideal. So, we do unbiased transcriptomics approach.

How would you run this with cell types?

seurat[["NEIGHBOURHOOD100"]] <- CreateAssayObject(t(sum_mtx))
DefaultAssay(seurat) <- "NEIGHBOURHOOD100"
seurat <- NormalizeData(seurat)
seurat <- ScaleData(seurat, features = rownames(seurat))
seurat <- RunPCA(seurat, features = rownames(seurat))
seurat <- FindNeighbors(seurat, reduction = "pca", dims = 1:10)
seurat <- FindClusters(seurat, resolution = 0.1, cluster.name = "Niches")

Lets visualise the detected “niches”. We can see that we have achieved a coarse partioning of the cells into crypt top, mid-crypt and crypt-base regions, as well as segmenting out follicles and sub-mucosal stroma.

How would you tweak the above approach to generate more or less granular niches?

ImageDimPlot(seurat, group.by = "Niches")

We can tabulate our detected niches with predicted cell type labels (or clusters) to visualise enrichment of different cell types across spatial niches.

For example, as could be expected, T-Cells and B-Cells enrich in Niche 2 (follicular).

comp <- table(seurat$Niches, seurat$predicted.id)
pheatmap(comp, scale="row")
comp <- table(seurat$Niches, seurat$SCT_snn_res.0.7)
pheatmap(comp, scale="row")

Detecting Adjacency Dependent Gene Expression Differences

What if we want to know how expression profiles of specific cell types change based on what they’re adjacent to? We can use the exactly same approach, except for each cell neighbourhood, we only aggregate the expression of cell types of interest.

So now for each cell, our matrix is the sum of expression values of nearby specific cell types.

As an example, here we will ask how expression of epithelial cells changes depending on what they are adjacent to. For simplicity, here we will use predicted cell type IDs from single cell reference data.

How would you select a different group of cells?

neighbors <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 10)
neighbors$nn <- neighbors$nn[coords$cell, coords$cell]
diag(neighbors$nn) <- 0 # dont count transcriptome of the cell itself, just neighbours?
mt <- LayerData(seurat, layer = "counts", assay = "XENIUM")[, coords$cell]
mt[, seurat$predicted.id != "Epithelium"] <- 0 # Zero counts for cells that are not of interest
sum_mtx <- as.matrix(neighbors$nn %*% t(mt))

As before, we store the aggregated matrix as a new assay, except here we further filter out any cells that do not have any epithelial cells nearby.

seurat[["EPI"]] <- CreateAssayObject(t(sum_mtx[rowSums(sum_mtx) > 0, ]))
DefaultAssay(seurat) <- "EPI"
seurat <- NormalizeData(seurat)

So, how does gene expression of epithelial cells change depending on whether they are in proximity to macrophages (cluster 9), or myofibroblasts (cluster 0).

First, lets visualise the spatial distribution of these cells:

Idents(seurat) <- "SCT_snn_res.0.7"
ImageDimPlot(seurat, cells = WhichCells(seurat,  expression=SCT_snn_res.0.7 %in% c( 9, 0)))

We can use FindMarkers function to identify differences between adjacent epithelial cells for these groups, if we run it on the aggregated EPI matrix:

markers <- FindMarkers(seurat, 0, 9) 
markers

Let’s visualise some of the top hits - we can see they separate nicely in epithelial cells:

VlnPlot(seurat, c("CA7",  "SMOC2"), idents = c(0, 9), assay = "EPI", pt.size = .1, alpha = 0.5)

As a sanity check, if we visualise actual, not epi-adjacent expression in myofibroblasts and macrophages, we can see that there’s hardly any expression at all. So, we are picking up epithelial signal

VlnPlot(seurat, c("CA7",  "SMOC2"), idents = c(0, 9), assay = "SCT", pt.size = .1, alpha = 0.5)

And a visual check

DefaultAssay(seurat) <- "SCT"
ImageFeaturePlot(seurat, "CA7") + scale_fill_viridis_c()
ImageFeaturePlot(seurat, "SMOC2") + scale_fill_viridis_c()

Finally, lets save all of our analysis as an RDS object

saveRDS(seurat, file="colon_in_situ.RDS")
sessionInfo()

###here we plot a percentage proportions of each cell type from integrated datasets 

library(Seurat) 
library(dplyr)
library(ggplot2)
 
meta.data <- CRC_merge[[]]
 
# Calculate percentage
percentages <- meta.data %>%
  group_by(orig.ident) %>%
  mutate(total_count = n()) %>%  # Get total counts per orig.ident
  group_by(predicted.id, orig.ident) %>%
  summarise(count = n(),
            percentage = (count / first(total_count)) * 100)  # Calculate percentage
 
# Plot percentages with labels
ggplot(percentages, aes(x = orig.ident, y = percentage, fill = predicted.id)) +
  geom_bar(stat = 'identity') +
  geom_text(aes(label = round(percentage, 1)),  # Add percentage labels rounded to 1 decimal
            position = position_stack(vjust = 0.5), size = 3) +  # Position labels inside bars
  ylab("Percentage") +
  ggtitle("Percentage of Predicted ID by Orig Ident") +
  theme_minimal()
LS0tCnRpdGxlOiAiSHVtYW4gQ29sb24gWGVuaXVtIGluIHNpdHUgU1QgRGF0YXNldCwgTnVjbGVpIFNlZ21lbnRhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCkZpcnN0IHdlIG5lZWQgdG8gc2V0IHVwIHRoZSBlbnZpcm9ubWVudCBhbmQgbG9hZCB0aGUgcGFja2FnZXMgd2Ugd2lsbCB1c2UgZm9yIHRoaXMgd29ya3Nob3AuIAoKKmxpYnJhcnkoU2V1cmF0KSo6IExvYWRzIHRoZSBTZXVyYXQgcGFja2FnZSwgd2hpY2ggaXMgYSBjb21wcmVoZW5zaXZlIHRvb2xraXQgZm9yIHNpbmdsZS1jZWxsIFJOQSBzZXF1ZW5jaW5nIGFuZCBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGFuYWx5c2lzLiBJdCBwcm92aWRlcyBhIHdpZGUgcmFuZ2Ugb2YgZnVuY3Rpb25zIGZvciBkYXRhIHByZXByb2Nlc3NpbmcsIG5vcm1hbGl6YXRpb24sIGNsdXN0ZXJpbmcsIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgYW5kIHZpc3VhbGl6YXRpb24uIEV4cGxvcmUgZG9jdW1lbnRhdGlvbiBoZXJlOiBodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0LwoKKmxpYnJhcnkoZ2dwbG90MikqOiBMb2FkcyB0aGUgZ2dwbG90MiBwYWNrYWdlLCBhIHBvd2VyZnVsIGFuZCBmbGV4aWJsZSBzeXN0ZW0gZm9yIGNyZWF0aW5nIHN0YXRpYyB2aXN1YWxpemF0aW9ucyBpbiBSLiBFeHBsb3JlIGRvY3VtZW50YXRpb24gaGVyZTogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvCgoqbGlicmFyeShzY0N1c3RvbWl6ZSkqOiBMb2FkcyB0aGUgc2NDdXN0b21pemUgcGFja2FnZSwgd2hpY2ggcHJvdmlkZXMgY3VzdG9tIGZ1bmN0aW9ucyBhbmQgdGhlbWVzIHRvIGVuaGFuY2UgdGhlIHZpc3VhbGl6YXRpb24gYW5kIGFuYWx5c2lzIGNhcGFiaWxpdGllcyBvZiBzaW5nbGUtY2VsbCBhbmQgc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YSwgb2Z0ZW4gaW4gY29uanVuY3Rpb24gd2l0aCBTZXVyYXQuIEV4cGxvcmUgZG9jdW1lbnRhdGlvbiBoZXJlOiBodHRwczovL3NhbXVlbC1tYXJzaC5naXRodWIuaW8vc2NDdXN0b21pemUvCgoqbGlicmFyeShyZWFkcikqOiBMb2FkcyByZWFkciBwYWNrYWdlIGZvciBmYXN0IGFuZCBmcmllbmRseSByZWFkaW5nIG9mIHJlY3Rhbmd1bGFyIGRhdGEsIHN1Y2ggYXMgQ1NWIGZpbGVzLCBpbnRvIFIuCgoqbGlicmFyeShwaGVhdG1hcCkqOiBMb2FkcyBwaGVhdG1hcCBwYWNrYWdlLCB3aGljaCBpcyBmb3IgY3JlYXRpbmcgcHJldHR5IGhlYXRtYXBzLCBvZmZlcmluZyBiZXR0ZXIgY29udHJvbCBvdmVyIGhlYXRtYXAgY3VzdG9taXphdGlvbiBjb21wYXJlZCB0byBiYXNlIFIuCgoqbGlicmFyeShtYXRyaXhTdGF0cykqOiBtYXRyaXhTdGF0cyBwcm92aWRlcyBoaWdobHkgb3B0aW1pemVkIGZ1bmN0aW9ucyBmb3IgbWF0cml4IG9wZXJhdGlvbnMsIHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIGNvbXB1dGluZyByb3cgYW5kIGNvbHVtbiBzdW1tYXJpZXMuIAoKKmxpYnJhcnkoc3BkZXApKjogc3BkZXAgc3RhbmRzIGZvciBTcGF0aWFsIERlcGVuZGVuY2UgYW5kIFNwYXRpYWwgQXV0b2NvcnJlbGF0aW9uLCBhbmQgaXQgcHJvdmlkZXMgZnVuY3Rpb25zIGZvciBzcGF0aWFsIGRhdGEgYW5hbHlzaXMsIGluY2x1ZGluZyBzcGF0aWFsIHdlaWdodHMgZ2VuZXJhdGlvbiwgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gc3RhdGlzdGljcywgYW5kIHNwYXRpYWwgcmVncmVzc2lvbi4KCipsaWJyYXJ5KGdlb2pzb25SKSogVGhlIGdlb2pzb25SIGxpYnJhcnkgaXMgdXNlZCBmb3IgaGFuZGxpbmcgR2VvSlNPTiBkYXRhIGluIFIuIEdlb0pTT04gaXMgYSBmb3JtYXQgZm9yIGVuY29kaW5nIGEgdmFyaWV0eSBvZiBnZW9ncmFwaGljIGRhdGEgc3RydWN0dXJlcyB1c2luZyBKYXZhU2NyaXB0IE9iamVjdCBOb3RhdGlvbiAoSlNPTikuIEl0IGlzIHNvbWV0aW1lcyB1c2VkIGFzIGEgZm9ybWF0IGZvciBzdG9yaW5nIGNlbGwgc2VnbWVudGF0aW9uIGJvdW5kYXJpZXMuCgpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY0N1c3RvbWl6ZSkKbGlicmFyeShyZWFkcikKbGlicmFyeShwaGVhdG1hcCkKbGlicmFyeShtYXRyaXhTdGF0cykKbGlicmFyeShzcGRlcCkKbGlicmFyeShnZW9qc29uUikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmBgYApTZXRzIHRoZSBwYXRoIHRvIHRoZSBkaXJlY3RvcnkgY29udGFpbmluZyB0aGUgWGVuaXVtIG91dHB1dCBkYXRhIC0gdGhpcyBpcyB0aGUgZGlyZWN0b3J5IHdoZXJlIGFsbCBvZiB0aGUgb3V0cHV0cyBhcmUgc3RvcmVkLgpgYGB7cn0KZGF0YV9kaXIgPC0gIi9wcm9qZWN0L3NoYXJlZC9zcGF0aWFsX2RhdGFfY2FtcC9kYXRhc2V0cy9EQVRBU0VUMi9YRU5JVU1fQ09MT1JFQ1RBTF9DQU5DRVIiCmBgYAoKKlJlYWRYZW5pdW0qIHJlYWRzIFhlbml1bSBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGZyb20gYSBzcGVjaWZpZWQgZGlyZWN0b3J5IHVzaW5nIGEgU2V1cmF0IHdyYXBwZXIgZnVuY3Rpb24gdGhhdCBzdXBwb3J0cyB0aGlzIGRhdGEgZm9ybWF0LiBYZW5pdW0gZGF0YSB0eXBpY2FsbHkgaW5jbHVkZXMgZXhwcmVzc2lvbiBtYXRyaWNlcyBhbmQgc3BhdGlhbCBjb29yZGluYXRlcywgYWxvbmcgd2l0aCBvdGhlciAgaW5mb3JtYXRpb24gYWJvdXQgY2VsbCBjZW50cm9pZHMgYW5kIHNlZ21lbnRhdGlvbnMgYW5kIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgdHJhbnNjcmlwdHMuIAoKKmRhdGFfZGlyKjogVGhlIHBhdGggdG8gdGhlIGRpcmVjdG9yeSBjb250YWluaW5nIHRoZSBYZW5pdW0gZGF0YSwgc2V0IGluIHRoZSBwcmV2aW91cyBzdGVwLgoqb3V0cyA9IGMoIm1hdHJpeCIsICJtaWNyb25zIikqOiBTcGVjaWZpZXMgdGhlIG91dHB1dHMgdG8gcmVhZCBmcm9tIHRoZSBkYXRhIGRpcmVjdG9yeS4gbWF0cml4IHJlZmVycyB0byBzdW1tYXJpc2VkIGNlbGwgYnkgZ2VuZSBtYXRyaXggYW5kIG1pY3JvbnMgcmVmZXJzIHRvIGluZGl2aWR1YWwgdHJhbnNjcmlwdCBjb29yZGluYXRlcy4KCip0eXBlID0gYygiY2VudHJvaWRzIiwgInNlZ21lbnRhdGlvbnMiKSo6IEluZGljYXRlcyB0aGUgdHlwZXMgb2Ygc3BhdGlhbCBpbmZvcm1hdGlvbiB0byBpbmNsdWRlIC0gaGVyZSwgd2UgYXJlIHJlYWRpbmcgaWIgYm90aCBjZWxsIGNlbnRyb2lkIGNvb3JkaW5hdGVzIGFuZCBjZWxsIGJvdW5kYXJ5IHNlZ21lbnRhdGlvbnMuCgoKYGBge3J9CmRhdGEgPC0gUmVhZFhlbml1bShkYXRhX2Rpciwgb3V0cyA9IGMoIm1hdHJpeCIsICJtaWNyb25zIiksIHR5cGU9YygiY2VudHJvaWRzIiwgInNlZ21lbnRhdGlvbnMiKSkKCmBgYApUaGlzIHByb3ZpZGVzIHVzIGEgbGlzdCBvZiBkYXRhOgpgYGB7cn0KbmFtZXMoZGF0YSkKYGBgCk1hdHJpeCBpcyBmdXJ0aGVyIHNwbGl0IGludG8gZ2VuZSBleHByZXNzaW9uIG1hdHJpeCBhbmQgdmFyaW91cyBjb250cm9sIHByb2JlcyBhbmQgY29kZXdvcmRzLiBEaWZmZXJlbnQgcGxhdGZvcm1zIGFuZCBwbGF0Zm9ybSB2ZXJzaW9ucyBpbmNsdWRlIGRpZmZlcmVudCBjb250cm9sIHByb2Jlcy4gQXMgdGhpcyB3aWxsIHZhcnksIGl0J3MgaW1wb3J0YW50IHRvIGNoZWNrIGFuZCB1bmRlcnN0YW5kIHdoYXQgdGhlIHNwZWNpZmljIGNvbnRyb2xzIGluIHlvdXIgb3duIGRhdGEgYXJlLiAgCgpIZXJlLCBuZWdhdGl2ZSBjb250cm9sIHByb2JlcyBhcmUgcHJvYmVzIHRoYXQgYXJlIGFkZGVkIHRvIHRoZSByZWFjdGlvbiBidXQgdGFyZ2V0IG5vbi1iaW9sb2dpY2FsIHNlcXVlbmNlcyBhbmQgc2hvdWxkIG5vdCBiaW5kIGFueSB0aXNzdWUgUk5BLiBOZWdhdGl2ZSBjb250cm9sIGNvZGV3b3JkcyBhcmUgdmFsaWQgY29kZXdvcmRzLCBidXQgbm8gcHJvYmVzIHdpdGggdGhhdCBjb2Rld29yZCBhZGRlZCB0byB0aGUgcmVhY3Rpb24uIFRoaXMgZWZmZWN0aXZlbHkgdGVsbHMgdXMgaG93IGdvb2QgdGhlIHRyYW5zY3JpcHQgY2FsbGluZyBhbGdvcml0aG0gaXMuCgpgYGB7cn0KbmFtZXMoZGF0YSRtYXRyaXgpCmBgYApSZWFkIGluIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGNlbGxzIC0gdGhpcyBnaXZlcyB1cyBwcmUtY2FsY3VsYXRlZCBpbmZvcm1hdGlvbiwgZm9yIGV4YW1wbGUgc2VnbWVudGVkIGNlbGwgb3IgbnVjbGV1cyBzaXplIGZvciBlYWNoIGNlbGwuCmBgYHtyfQpjZWxsX21ldGFfZGF0YSA8LSByZWFkLmNzdihmaWxlLnBhdGgoZGF0YV9kaXIsICJjZWxscy5jc3YuZ3oiKSkKcm93bmFtZXMoY2VsbF9tZXRhX2RhdGEpIDwtIGNlbGxfbWV0YV9kYXRhJGNlbGxfaWQKaGVhZChjZWxsX21ldGFfZGF0YSkKYGBgCgpXZSB3aWxsIHN0YXJ0IGJ5IGNyZWF0aW5nIGEgYmFzaWMgc2V1cmF0IG9iamVjdCBmcm9tIHRoZSBkYXRhLiAKCipDcmVhdGVTZXVyYXRPYmplY3QqIGZ1bmN0aW9uIGluaXRpYWxpemVzIGEgU2V1cmF0IG9iamVjdCB1c2luZyB0aGUgcHJvdmlkZWQgZ2VuZSBleHByZXNzaW9uIG1hdHJpeCBhbmQgb3B0aW9uYWwgbWV0YWRhdGEuCgoqY291bnRzKjogVGhlIGdlbmUgZXhwcmVzc2lvbiBtYXRyaXgsIHdoaWNoIGNvbnRhaW5zIHRoZSByYXcgY291bnQgZGF0YSBmb3IgZWFjaCBnZW5lIGluIGVhY2ggY2VsbC4KKmRhdGEkbWF0cml4W1siR2VuZSBFeHByZXNzaW9uIl1dKjogU3BlY2lmaWVzIHRoZSBnZW5lIGV4cHJlc3Npb24gbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBsb2FkZWQgWGVuaXVtIGRhdGEuIEhlcmUsIHdlIGxlYXZlIG91dCB0aGUgY29udHJvbCBwcm9iZXMgZm9yIG5vdy4gCgoqYXNzYXkqOiBUaGUgbmFtZSBvZiB0aGUgYXNzYXkgLSB5b3UgY2FuIGNhbGwgaXQgYW55dGhpbmcgeW91IGxpa2UuIEhlcmUsIHdlIGdvIHdpdGggIlhFTklVTSIuIAoKKm1ldGEuZGF0YSo6IE1ldGFkYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgY2VsbHMgb3Igc3BvdHMuIEhlcmUsIHdlIGFkZCB0aGUgY2VsbCBzdGF0aXN0aWNzIHdlIHJlYWQgaW4gZWFybGllciBhcyAqY2VsbF9tZXRhX2RhdGEqLgoKQnkgcHJpbnRpbmcgdGhlICpzZXVyYXQqIG9iamVjdCwgd2UgY2FuIHNlZSB0aGF0IHdlIHJlYWQgaW4gfiAzMCwwMDAgY2VsbHMgd2l0aCBtZWFzdXJlcyBmb3IgMzI1IGdlbmVzCgpgYGB7cn0Kc2V1cmF0IDwtIENyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBkYXRhJG1hdHJpeFtbIkdlbmUgRXhwcmVzc2lvbiJdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiWEVOSVVNIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0YS5kYXRhID0gY2VsbF9tZXRhX2RhdGEpCnNldXJhdApgYGAKCkFkZGluZyBzcGF0aWFsIGNvb3JkaW5hdGVzIHRvIGEgU2V1cmF0IG9iamVjdCBhbGxvd3MgZm9yIHNwYXRpYWxseSByZXNvbHZlZCBhbmFseXNpcyBhbmQgdmlzdWFsaXphdGlvbi4gVGhpcyByZXF1aXJlcyBjcmVhdGluZyBvYmplY3RzIGZvciBjZW50cm9pZHMgYW5kIHNlZ21lbnRhdGlvbnMgd2UgcmVhZCBpbiBlYXJsaWVyLCBhbmQgdGhlbiBpbnRlZ3JhdGluZyB0aGVzZSB3aXRoIHRoZSBtYWluIFNldXJhdCBvYmplY3QuCgoqQ3JlYXRlRk9WKjogVGhpcyBmdW5jdGlvbiBjcmVhdGVzIGEgZmllbGQgb2YgdmlldyAoRk9WKSBvYmplY3QgdGhhdCBpbmNsdWRlcyBzcGF0aWFsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBjZW50cm9pZHMsIHNlZ21lbnRhdGlvbnMsIGFuZCBtb2xlY3VsZSBjb29yZGluYXRlcy4gQW4gRk9WIGNhbiBiZSB0aGUgZW50aXJlIHNsaWRlLCBvciBhIHNlbGVjdGVkIHJlZ2lvbiB3aXRoaW4gYSBzbGlkZSAtIGkuZS4gaXQgZG9lcyBub3QgbmVlZCB0byBoYXZlIGVudHJpZXMgZm9yIGFsbCB0aGUgY2VsbHMgaW4gdGhlIHNldXJhdCBvYmplY3QuCgoqY29vcmRzKjogQSBsaXN0IGNvbnRhaW5pbmcgdGhlIGNlbnRyb2lkcyBhbmQvb3Igc2VnbWVudGF0aW9uIGRhdGEuIEZvciBsYXJnZXIgZGF0YXNldHMsIGl0IGNhbiBiZSBxdWlja2VyIHRvIG9ubHkgbG9hZCBjZW50cm9pZHMsIGFzIHRoaXMgbWluaW1pc2VzIHRoZSBhbW91bnQgb2YgZGF0YSBwb2ludHMuIAoKKmNlbnRyb2lkcyA9IENyZWF0ZUNlbnRyb2lkcyhkYXRhJGNlbnRyb2lkcykqOiBDcmVhdGVzIGEgY2VudHJvaWRzIG9iamVjdCBmcm9tIHRoZSBjZW50cm9pZCBkYXRhIGluIHRoZSBYZW5pdW0gZGF0YXNldC4KKnNlZ21lbnRhdGlvbiA9IENyZWF0ZVNlZ21lbnRhdGlvbihkYXRhJHNlZ21lbnRhdGlvbnMpKjogQ3JlYXRlcyBhIHNlZ21lbnRhdGlvbiBvYmplY3QgZnJvbSB0aGUgc2VnbWVudGF0aW9uIGRhdGEgaW4gdGhlIFhlbml1bSBkYXRhc2V0LgoKKnR5cGUgPSBjKCJzZWdtZW50YXRpb24iLCAiY2VudHJvaWRzIikqOiBTcGVjaWZpZXMgdGhlIHR5cGVzIG9mIHNwYXRpYWwgZGF0YSBiZWluZyBpbmNsdWRlZCwgd2hpY2ggYXJlIHNlZ21lbnRhdGlvbiBhbmQgY2VudHJvaWQgZGF0YS4KCiptb2xlY3VsZXMgPSBkYXRhJG1pY3JvbnMqOiBUaGUgc3BhdGlhbCBjb29yZGluYXRlcyBvZiBpbmRpdmlkdWFsIHRyYW5zY3JpcHRzL21vbGVjdWxlcyBpbiB0aGUgZGF0YS4gVGhpcyBpcyBvcHRpb25hbCAtIGZvciBsYXJnZXIgZGF0YXNldHMsIHNraXBwaW5nIHRyYW5zY3JpcHQgY29vcmRpbmF0ZXMgY2FuIGJlIGEgZ29vZCBpZGVhLgoKKnNldXJhdFtbIkNPTE9OIl1dIDwtIGNvb3Jkcyo6IEFkZHMgdGhlIGNyZWF0ZWQgRk9WIG9iamVjdCB0byB0aGUgU2V1cmF0IG9iamVjdCB1bmRlciB0aGUgbmV3IEZPViBuYW1lICJDT0xPTiIuIFRoaXMgY2FuIGJlIG5hbWVkIChhbG1vc3QpIGFueXRoaW5nIC0gYnV0LCBhdm9pZCB1c2luZyB1bmRlcnNjb3JlcyBhcyB0aGlzIGNhbiBjcmVhdGUgc29tZSB1bmV4cGVjdGVkIGJlaGF2aW91cnMgbGF0ZXIuCgpUSVA6ICpMb2FkWGVuaXVtKCkqIGlzIGEgd3JhcHBlciB0aGF0IHdvdWxkIGxvYWQgaW4gYm90aCBjZWxsIGNvdW50cyBtYXRyaXggYW5kIHNwYXRpYWwgY29vcmRpbmF0ZXMgaW4gb25lIGZ1bmN0aW9uLCBzaW1wbGlmeWluZyB0aGVzZSBzdGVwcy4gSG93ZXZlciwgKmluIHNpdHUqIHBsYXRmb3JtcyBhcmUgZXZvbHZpbmcgYXQgYSB2ZXJ5IGZhc3QgcmF0ZSBhbmQgdGhlcmUgYXJlIGNvbnN0YW50IGNoYW5nZXMgb24gaG93IHRoZSBkYXRhIGlzIHN0b3JlZCwgaW4gcGFydGljdWxhciBmb3IgZmlsZSBmb3JtYXRzIGZvciBjZWxsIHNlZ21lbnRhdGlvbiBhbmQgY29vcmRpbmF0ZXMuIEhlcmUsIHdlIGhhdmUgYnJva2VuIGRvd24gdGhlIHN0ZXBzIHRvIHNob3cgaG93IHRvIGFzc2VtYmxlIGFuIGluIHNpdHUgc2V1cmF0IG9iamVjdCBmcm9tIHRoZSBrZXkgY29tcG9uZW50cywgaW4gY2FzZSB0aGUgcGxhdGZvcm0gc3BlY2lmaWMgcmVhZGVycyBkb24ndCB3b3JrIGZvciB5b3VyIHNwZWNpZmljIGRhdGEuCmBgYHtyfQpjb29yZHMgPC0gQ3JlYXRlRk9WKGNvb3JkcyA9IGxpc3QoY2VudHJvaWRzID0gQ3JlYXRlQ2VudHJvaWRzKGRhdGEkY2VudHJvaWRzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWdtZW50YXRpb24gPSBDcmVhdGVTZWdtZW50YXRpb24oZGF0YSRzZWdtZW50YXRpb25zKSksCiAgICAgICAgICAgICAgICAgICAgdHlwZSA9IGMoInNlZ21lbnRhdGlvbiIsICJjZW50cm9pZHMiKSwKICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJYRU5JVU0iKQpzZXVyYXRbWyJDT0xPX0NBTiJdXSA8LSBjb29yZHMgIApgYGAKCkluc3BlY3QgdGhlIG9iamVjdCAtIG5vdywgeW91IGNhbiBzZWUgd2UgaGF2ZSBhZGRlZCBhIHNwYXRpYWwgZmllbGQgb2YgdmlldzoKYGBge3J9CnNldXJhdAoKI2Nyb3AgdGhlIGltYWdlIHRvIGNoZWNrIHdoaWNoIGNvb3JkaW5hdGVzIGFyZSBuZWVkZWQKY3JvcHBlZCA8LSBDcm9wKHNldXJhdFtbIkNPTE9fQ0FOIl1dLCB4ID0gYyg0MDAwLCAyMDAwKSwgeSA9IGMoMTAwMCwgNDAwMCksIGNvb3JkcyA9ICJwbG90IikKc2V1cmF0W1siQ1JDMSJdXSA8LSBjcm9wcGVkCgpJbWFnZURpbVBsb3Qoc2V1cmF0LGZvdiA9ICJDUkMxIiwgYXhlcyA9IFRSVUUpCgoKCnNldXJhdF9DUkMxIDwtIHNldXJhdFssc2V1cmF0JHhfY2VudHJvaWQgPCA0MDAwICYgc2V1cmF0JHhfY2VudHJvaWQgPiAyMDAwICYKICAgICAgICAgICAgICAgICAgICAgICAgc2V1cmF0JHlfY2VudHJvaWQgPDQwMDAgJiBzZXVyYXQkeV9jZW50cm9pZCA+IDEwMDBdCgoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMxLCBheGVzPVRSVUUpCgpgYGAKQWRkaW5nIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMgYXMgc2VwYXJhdGUgYXNzYXlzIGluIHRoZSBTZXVyYXQgb2JqZWN0IGFsbG93cyBmb3IgdGhlIHRyYWNraW5nIGFuZCBhbmFseXNpcyBvZiB0ZWNobmljYWwgYXJ0aWZhY3RzIGFuZCBub2lzZSB3aXRoaW4geW91ciBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhLCB3aGlsZSBrZWVwaW5nIHRoZXNlIG91dHB1dHMgc2VwYXJhdGUgZnJvbSB0aGUgbWFpbiBiaW9sb2dpY2FsIGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMuCgoKKipVbmFzc2lnbmVkIGNvZGV3b3JkcyoqIGFyZSB1bnVzZWQgY29kZXdvcmRzLiBUaGVyZSBpcyBubyBwcm9iZSBpbiBhIHBhcnRpY3VsYXIgZ2VuZSBwYW5lbCB0aGF0IHdpbGwgZ2VuZXJhdGUgdGhlIGNvZGV3b3JkLgoKKipOZWdhdGl2ZSBjb250cm9sIHByb2JlcyoqIGFyZSBwcm9iZXMgdGhhdCBleGlzdCBpbiB0aGUgcGFuZWxzIGJ1dCB0YXJnZXQgbm9uLWJpb2xvZ2ljYWwgc2VxdWVuY2VzLiBUaGV5IGNhbiBiZSB1c2VkIHRvIGFzc2VzcyB0aGUgc3BlY2lmaWNpdHkgb2YgdGhlIGFzc2F5LgoKKipOZWdhdGl2ZSBjb250cm9sIGNvZGV3b3JkcyoqIGFyZSBjb2Rld29yZHMgaW4gdGhlIGNvZGVib29rIHRoYXQgZG8gbm90IGhhdmUgYW55IHByb2JlcyBtYXRjaGluZyB0aGF0IGNvZGUuIFRoZXkgYXJlIGNob3NlbiB0byBtZWV0IHRoZSBzYW1lIHJlcXVpcmVtZW50cyBhcyByZWd1bGFyIGNvZGV3b3JkcyBhbmQgY2FuIGJlIHVzZWQgdG8gYXNzZXNzIHRoZSBzcGVjaWZpY2l0eSBvZiB0aGUgZGVjb2RpbmcgYWxnb3JpdGhtLgoKCmBgYHtyfQpzZXVyYXRfQ1JDMVtbIk5lZ2F0aXZlLkNvbnRyb2wuQ29kZXdvcmQiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoY291bnRzID0gZGF0YSRtYXRyaXhbWyJOZWdhdGl2ZSBDb250cm9sIENvZGV3b3JkIl1dWywgV2hpY2hDZWxscyhzZXVyYXRfQ1JDMSldKQpzZXVyYXRfQ1JDMVtbIk5lZ2F0aXZlLkNvbnRyb2wuUHJvYmUiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoY291bnRzID0gZGF0YSRtYXRyaXhbWyJOZWdhdGl2ZSBDb250cm9sIFByb2JlIl1dWywgV2hpY2hDZWxscyhzZXVyYXRfQ1JDMSldKQpzZXVyYXRfQ1JDMVtbIlVuYXNzaWduZWQuQ29kZXdvcmQiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoY291bnRzID0gZGF0YSRtYXRyaXhbWyJVbmFzc2lnbmVkIENvZGV3b3JkIl1dWywgV2hpY2hDZWxscyhzZXVyYXRfQ1JDMSldKQoKYGBgCgpJbnNwZWN0IHRoZSBvYmplY3Q6CmBgYHtyfQpzZXVyYXRfQ1JDMQpgYGAKTGV0J3Mgc3RhcnQgd2l0aCBzb21lIGJhc2ljIFFDIGFuZCB2aXN1YWxpc2F0aW9uIG9mIHRoZSBkYXRhLiAKCkluIFNldXJhdCwgKmluIHNpdHUqIHNwYXRpYWwgdHJhbnNjcmlwdG9taWNzIGNvdW50ZXJwYXJ0IGZ1bmN0aW9ucyB0byAqJ1NwYXRpYWxEaW1QbG90JyogYW5kIConU3BhdGlhbEZlYXR1cmVQbG90Jyogd2UgY292ZXJlZCB5ZXN0ZXJkYXkgYXJlIGNhbGxlZCAqJ0ltYWdlRmVhdHVyZVBsb3QnKiBhbmQgKidJbWFnZURpbVBsb3QnKi4gVGhlc2UgaGF2ZSBhZGRpdGlvbmFsIGZ1bmN0aW9uYWxpdHkgdG8gcGxvdCBjZWxsIHNlZ21lbnRhdGlvbnMgYW5kIGluZGl2aWR1YWwgdHJhbnNjcmlwdCBjb29yZGluYXRlcywgYnV0IG90aGVyd2lzZSBmdW5jdGlvbiBleGFjdGx5IHRoZSBzYW1lIGFzIHRoZSBzZXF1ZW5jaW5nIGJhc2VkIFNUIGNvdW50ZXJwYXJ0cy4gCgpGaXJzdCwgbGV0cyB2aXN1YWxpc2UgdGhlIHRvdGFsIHRyYW5zY3JpcHRzIGRldGVjdGVkIHBlciBjZWxsLgoKQXMgaW4gc2NSTkEtU2VxIGRhdGEsIHRoaXMgaXMgdGhlIG1vc3QgYmFzaWMgbWVhc3VyZSBvZiBvdmVyYWxsIHNpZ25hbCBhbmQgaG93IHdlbGwgdGhlIGRhdGEgbG9va3MuIAoKVW5saWtlIGluIHNjUk5BLVNlcSBkYXRhIG9yIHVuYmlhc2VkIHNlcXVlbmNpbmctYmFzZWQgU1QsIHRoZXNlIG1lYXN1cmVzIGFyZSBhbHNvIHZlcnkgaGVhdmlseSBkZXBlbmRlbnQgbm90IG9ubHkgb24gdGhlIHRvdGFsIFJOQSBxdWFudGl0eSBvZiBlYWNoIGNlbGwgYW5kIHRpc3N1ZSBxdWFsaXR5LCBidXQgYWxzbyBvbiB0aGUgdGFyZ2V0IHBhbmVsIHVzZWQgZm9yIHRoZSBleHBlcmltZW50LiBVbmRlci1yZXByZXNlbnRlZCBjZWxsIHR5cGVzIHdpbGwgbmF0dXJhbGx5IHlpZWxkIGZld2VyIHRyYW5zY3JpcHRzLgpGaW5hbGx5LCB0aGUgcXVhbGl0eSBvZiBjZWxsIHNlZ21lbnRhdGlvbiBhbHNvIHBsYXlzIGEgcm9sZS4KCkluIHRoaXMgY2FzZSwgd2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBhcmVhcyB3aXRoIGhpZ2hlciBhbmQgbG93ZXIgdG90YWwgdHJhbnNjcmlwdHMgZGV0ZWN0ZWQuIAoKVW5kZXJzdGFuZGluZyB5b3VyIHRpc3N1ZSBhbmQgdGFyZ2V0IHBhbmVsIGhlcmUgaXMgaW1wb3J0YW50IHRvIGRlbGluZWF0ZSB3aGVyZSB0aGVzZSBkaWZmZXJlbmNlcyBhcmUgYmlvbG9naWNhbCBhbmQgd2hlcmUgdGhleSBtYXkgYmUgdGVjaG5pY2FsLgoKYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzEsICJuQ291bnRfWEVOSVVNIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYApTaW1pbGFybHksIHdlIGNhbiB2aXN1YWxpc2UgdGhlIHRvdGFsIG51bWJlciBvZiBnZW5lIGRldGVjdGVkIHBlciBjZWxsLiBZb3UgY2FuIHNlZSB0aGF0IHRoaXMgaXMgYSBiaXQgbGVzcyB2YXJpYWJsZSBhY3Jvc3MgdGlzc3VlLiAKCgpgYGB7cn0KSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIm5GZWF0dXJlX1hFTklVTSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKClRoaXMgY29kZSBleGFtaW5lcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgKGdlbmVzKSBkZXRlY3RlZCBwZXIgY2VsbCBpbiB0aGUgU2V1cmF0IG9iamVjdCB1c2luZyBhIGRlbnNpdHkgcGxvdCBhbmQgY2FsY3VsYXRlcyBzcGVjaWZpYyBxdWFudGlsZXMgb2YgdGhpcyBkaXN0cmlidXRpb24uIFRoaXMgaXMgaW1wb3J0YW50IGZvciB1bmRlcnN0YW5kaW5nIHRoZSB2YXJpYWJpbGl0eSBhbmQgZGlzdHJpYnV0aW9uIG9mIGRldGVjdGVkIGZlYXR1cmVzLCB3aGljaCBjYW4gaGVscCBpZGVudGlmeSBwb3RlbnRpYWwgaXNzdWVzIHN1Y2ggYXMgbG93LXF1YWxpdHkgY2VsbHMgYW5kIGRldGVybWluZSBhbnkgZmlsdGVyaW5nIHRocmVzaG9sZHMgdGhhdCBtYXkgbmVlZCB0byBiZSBhcHBsaWVkLgoKSWYgeW91J3JlIGNvbWluZyBmcm9tIHNjUk5BLVNlcSB3b3JrLCB0aGVzZSBsb3cgbnVtYmVycyBwcm9iYWJseSBsb29rIHZlcnkgYWxhcm1pbmcuIEhvdyBjYW4geW91IHBvc3NpYmx5IHdvcmsgd2l0aCAzMSBtZWRpYW4gZ2VuZXMgcGVyIGNlbGw/CgpVbmxpa2Ugc2NSTkEtU2VxIGRhdGEgYW5kIHNlcXVlbmNpbmctYmFzZWQgU1QsIGJvdGggZ2VuZSBkcm9wb3V0cyBhbmQgbm9pc2UgYXJlIG11Y2gsIG11Y2ggbG93ZXIgaW4gKmluIHNpdHUqIFNUIGRhdGEuIAoKV2UgYXJlIGFsc28gd29ya2luZyB3aXRoIDEwMC1mb2xkIGZld2VyIHRhcmdldHRlZCBnZW5lcy4KCgpgYGB7cn0KZ2dwbG90KHNldXJhdFtbXV0sIGFlcyhuRmVhdHVyZV9YRU5JVU0pKSArIGdlb21fZGVuc2l0eSgpCnF1YW50aWxlKHNldXJhdCRuRmVhdHVyZV9YRU5JVU0sIGMoMC4wMSwgMC4xLCAwLjUsIDAuOSwgMC45OSkpCmBgYApVc2luZyAqSW1hZ2VGZWF0dXJlUGxvdCogdG8gdmlzdWFsaXplIHRoZSBjZWxsIGFyZWEgaW4gc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YSBhbGxvd3MgdXMgdG8gZXhhbWluZSB0aGUgc3BhdGlhbCBvcmdhbml6YXRpb24gYW5kIHBvdGVudGlhbCBoZXRlcm9nZW5laXR5IG9mIGNlbGwgc2l6ZXMgd2l0aGluIHlvdXIgdGlzc3VlIHNhbXBsZS4KCioqV2h5IGRvIHdlIGdldCBzdWNoIGEgZGlmZmVyZW5jZSBpbiBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiBjZWxsIHNpemVzPyoqCgpUaGlzIGNvdWxkIGJlIGR1ZSB0byBiaW9sb2dpY2FsIGRpZmZlcmVuY2VzIGJldHdlZW4gc21hbGwgYW5kIGxhcmdlIGNlbGxzIC0gZS5nLiBzbWFsbCBjZWxscyBsaWtlIFQtY2VsbHMuIAoKSG93ZXZlciwgaGVyZSB0aGUgc2lnbmFsIGNvcnJlbGF0ZXMgd2l0aCBhcmVhcyBvZiBsb3cgY2VsbHVsYXJpc2F0aW9uLiBUaGVyZWZvcmUsIGl0IGlzIGxpa2VseSB0aGlzIGlzIGFuIGFydGVmYWN0IG9mIG51Y2xlaSBleHBhbnNpb24gaW4gY2VsbCBzZWdtZW50YXRpb24uIAoKV2hhdCBpcyBOdWNsZWkgRXhwYW5zaW9uPwoKTnVjbGVpIGV4cGFuc2lvbiBpbiBjZWxsIHNlZ21lbnRhdGlvbiByZWZlcnMgdG8gdGhlIHByb2Nlc3Mgb2YgZW5sYXJnaW5nIHRoZSBzZWdtZW50ZWQgbnVjbGVpIHJlZ2lvbnMgdG8gYXBwcm94aW1hdGUgdGhlIGJvdW5kYXJpZXMgb2YgdGhlIGVudGlyZSBjZWxscy4gVGhpcyB0ZWNobmlxdWUgaXMgdXNlZCB0byBiZXR0ZXIgcmVwcmVzZW50IHRoZSBhY3R1YWwgY2VsbCBib3VuZGFyaWVzIHdoZW4gb25seSB0aGUgbnVjbGVpIGhhdmUgYmVlbiBleHBsaWNpdGx5IHNlZ21lbnRlZC93ZSBvbmx5IGhhdmUgREFQSSBhbmQgbm8gYWRkaXRpb25hbCBjZWxsIGJvdW5kYXJ5IHN0YWluaW5nLiBUaGUgcHJpbWFyeSBnb2FsIGlzIHRvIHByb3ZpZGUgYSBtb3JlIGFjY3VyYXRlIGVzdGltYXRpb24gb2YgdGhlIGNlbGx1bGFyIGFyZWEsIHdoaWNoIGlzIGNydWNpYWwgZm9yIHZhcmlvdXMgZG93bnN0cmVhbSBhbmFseXNlcyBpbiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBhbmQgc2luZ2xlLWNlbGwgc3R1ZGllcy4gSW4gdGhpcyBjYXNlLCBudWNsZWkgZXhwYW5zaW9uIGlzIGNvbnN0cmFpbmVkIGVpdGhlciBieSBtYXhpbXVtIGRpc3RhbmNlIG9yIG90aGVyIG5lYXJieSBjZWxscyAtIHNvLCB3aGVyZSB0aGVyZSBhcmUgbm8gb3RoZXIgbmVhcmJ5IGNlbGxzIHRvICJidW1wIGludG8iLCB0aGUgZXhwYW5zaW9uIGdlbmVyYXRlcyBhcnRpZmljaWFsbHkgYmlnZ2VyIGNlbGxzLgoKCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCAiY2VsbF9hcmVhIiwgYXhlcyA9IFQpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKV2UgY2FuIGZ1cnRoZXIgY2hlY2sgdGhhdCB0aGlzIGlzIGxpa2VseSB0aGUgY2FzZSBieSBwbG90dGluZyB0aGUgcmF0aW8gYmV0d2VlbiBudWNsZWkgYW5kIHRvdGFsIGNlbGwgYXJlYS4gV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGlzIGEgdmVyeSBiaWcgZGVjcmVhc2UgaW4gcGVyY2VudGFnZSBvZiBjZWxsIGFyZWEgb2NjdXBpZWQgYnkgbnVjbGV1cyBpbiBhcmVhcyBvZiBsb3cgY2VsbCBkZW5zaXR5LgoKVGhlIGNlbGwtdG8tbnVjbGV1cyBhcmVhIHJhdGlvIGNhbiBhbHNvIHBvdGVudGlhbGx5IHByb3ZpZGUgaW5zaWdodHMgaW50byBjZWxsIG1vcnBob2xvZ3ksIGNlbGwgdHlwZSBhbmQgcG90ZW50aWFsIGNoYW5nZXMgaW4gY2VsbHVsYXIgc3RhdGVzIG9yIGNvbmRpdGlvbnMuIEZvciBleGFtcGxlLCBULUNlbGxzIGNhbiBvZnRlbiBiZSBxdWl0ZSB3ZWxsIGlkZW50aWZpZWQgYnkgdGhpcyB2YXJpYWJsZSBhbG9uZSwgYXMgdGhleSBoYXZlIGEgc21hbGwgY3l0b3BsYXNtIHZvbHVtZS4gIEhvd2V2ZXIsIHdpdGhvdXQgYSBjZWxsIGJvdW5kYXJ5IHN0YWluLCB0aGlzIG1ldHJpYyBtYWlubHkgY2FwdHVyZXMgc2VnbWVudGF0aW9uIGFydGVmYWN0cywgc28gYmUgY2FyZWZ1bCBhYm91dCBvdmVyLWludGVycHJldGF0aW9uIQoKYGBge3J9CnNldXJhdF9DUkMxJGNlbGxfbnVjbGV1c19yYXRpbyA8LSBzZXVyYXRfQ1JDMSRudWNsZXVzX2FyZWEgLyBzZXVyYXRfQ1JDMSRjZWxsX2FyZWEKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgImNlbGxfbnVjbGV1c19yYXRpbyIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCklmIHdlIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiwgd2Ugc2VlIHRoYXQgd2UgaGF2ZSBhIGJpZyB0YWlsIGVuZCBvZiBvdmVybHkgbGFyZ2UgY2VsbHMuCgpgYGB7cn0KZ2dwbG90KHNldXJhdF9DUkMxW1tdXSwgYWVzKGNlbGxfYXJlYSkpICsgZ2VvbV9kZW5zaXR5KCkKYGBgCkluIHRoaXMgY2FzZSwgd2UgY2FuIHNlZSB0aGF0IGFzIGV4cGVjdGVkLCB0aGVyZSBpcyBnZW5lcmFsbHkgYSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGNlbGwgYXJlYSBhbmQgdHJhbnNjcmlwdCBkZXRlY3Rpb24gcmF0ZS4gCgpIb3dldmVyLCB3ZSBhbHNvIGhhdmUgYSBncm91cCBvZiBjZWxscyB3aGVyZSB0aGlzIGlzIG5vdCB0aGUgY2FzZSAtIHZlcnkgbGFyZ2UgY2VsbHMgYnV0IHJlbGF0aXZlbHkgZmV3IHRyYW5zY3JpcHRzLiBUaGVzZSBjZWxscyBhcmUgbWFpbmx5IHN1Ym11Y29zYWwgc3Ryb21hbCBjZWxscyB3aGljaCBhcmUgdmVyeSBwb29ybHkgY292ZXJlZCBieSB0aGUgcGFuZWwgMTB4IGhhdmUgdXNlZC4gCgoKYGBge3J9CmdncGxvdChzZXVyYXRfQ1JDMVtbXV0sIGFlcyhuQ291bnRfWEVOSVVNLCBjZWxsX2FyZWEpKSArIGdlb21fcG9pbnQoKSAKYGBgCldlIGNhbiBjcmVhdGUgYSBmaWx0ZXIgdG8gcmVtb3ZlIHRoZSBvdmVybHkgbGFyZ2UgY2VsbHMgZnJvbSB0aGUgYW5hbHlzaXMuCgoqcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC45OSkqOiBDYWxjdWxhdGVzIHRoZSA5OXRoIHBlcmNlbnRpbGUgb2YgdGhlIGNlbGxfYXJlYSB2YWx1ZXMgaW4gdGhlIFNldXJhdCBvYmplY3QuIFRoaXMgdmFsdWUgc2VydmVzIGFzIGEgdGhyZXNob2xkIHRvIGlkZW50aWZ5IHRoZSBsYXJnZXN0IDElIG9mIGNlbGxzIC0gYnV0IHdoYXQgaXMgYSBzZW5zaWJsZSB0aHJlc2hvbGQsIGlmIGFueSwgZGVwZW5kcyBvbiB5b3VyIHRpc3N1ZS4KCipzZXVyYXQkY2VsbF9hcmVhIDwgcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC45OSkqOiBDb21wYXJlcyBlYWNoIGNlbGwncyBhcmVhIHRvIHRoZSA5OXRoIHBlcmNlbnRpbGUgdGhyZXNob2xkLiBUaGUgcmVzdWx0IGlzIGEgbG9naWNhbCB2ZWN0b3Igd2hlcmUgZWFjaCBlbGVtZW50IGlzIFRSVUUgaWYgdGhlIGNvcnJlc3BvbmRpbmcgY2VsbCdzIGFyZWEgaXMgbGVzcyB0aGFuIHRoZSA5OXRoIHBlcmNlbnRpbGUgYW5kIEZBTFNFIG90aGVyd2lzZS4KCgoqc2V1cmF0W1siU0laRV9GSUxURVJfTEFSR0UiXV0qOiBDcmVhdGVzIGEgbmV3IG1ldGFkYXRhIGZpZWxkIG5hbWVkIFNJWkVfRklMVEVSX0xBUkdFIGluIHRoZSBTZXVyYXQgb2JqZWN0LCBzdG9yaW5nIHRoZSBsb2dpY2FsIHZlY3Rvci4KCgpgYGB7cn0Kc2V1cmF0X0NSQzFbWyJTSVpFX0ZJTFRFUl9MQVJHRSJdXSA8LSBzZXVyYXRfQ1JDMSRjZWxsX2FyZWEgPCBxdWFudGlsZShzZXVyYXRfQ1JDMSRjZWxsX2FyZWEsIC45OSkKYGBgCgpOb3cgd2UgY2FuIHVzZSAqSW1hZ2VEaW1QbG90KiB0byB2aXN1YWxpc2UgdGhlIGNlbGxzIHdoaWNoIGhhdmUgYmVlbiBmbGFnZ2VkIGZvciByZW1vdmFsLgoKV2UgY2FuIHNlZSB0aGF0IHRoZXNlIGFyZSBtb3N0bHkgaW4gdGhlIHN1Ym11Y29zYSByZWdpb24uIAoKKipIb3cgZG8gZGlmZmVyZW50IHRocmVzaG9sZHMgYmVoYXZlPyBJcyB0aGVyZSBhIG1vcmUgYXBwcm9wcmlhdGUgb25lIHRvIHVzZT8gSXMgYW55IG5lY2Vzc2FyeSBhdCBhbGw/KioKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzEsIGdyb3VwLmJ5PSJTSVpFX0ZJTFRFUl9MQVJHRSIpCmBgYApXZSBjYW4gdXNlIHRoZSBzYW1lIGFwcHJvYWNoIHRvIGNyZWF0ZSBhIGZpbHRlciBmb3Igc2VnbWVudGVkIGNlbGxzIHdoaWNoIGFyZSB2ZXJ5IHNtYWxsIGFuZCBsaWtlbHkgc2VnbWVudGF0aW9uIGFyZmV0YWN0cy4gCgoqcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC4wMSkqOiBDYWxjdWxhdGVzIHRoZSAxc3QgcGVyY2VudGlsZSBvZiB0aGUgY2VsbF9hcmVhIHZhbHVlcyBpbiB0aGUgU2V1cmF0IG9iamVjdC4gVGhpcyB2YWx1ZSBzZXJ2ZXMgYXMgYSB0aHJlc2hvbGQgdG8gaWRlbnRpZnkgdGhlIHNtYWxsZXN0IDElIG9mIGNlbGxzLgoKKnNldXJhdCRjZWxsX2FyZWEgPiBxdWFudGlsZShzZXVyYXQkY2VsbF9hcmVhLCAwLjAxKSo6IENvbXBhcmVzIGVhY2ggY2VsbCdzIGFyZWEgdG8gdGhlIDFzdCBwZXJjZW50aWxlIHRocmVzaG9sZC4gVGhlIHJlc3VsdCBpcyBhIGxvZ2ljYWwgdmVjdG9yIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBUUlVFIGlmIHRoZSBjb3JyZXNwb25kaW5nIGNlbGwncyBhcmVhIGlzIGdyZWF0ZXIgdGhhbiB0aGUgMXN0IHBlcmNlbnRpbGUgYW5kIEZBTFNFIG90aGVyd2lzZS4KCipzZXVyYXRbWyJTSVpFX0ZJTFRFUl9TTUFMTCJdXSo6IENyZWF0ZXMgYSBuZXcgbWV0YWRhdGEgZmllbGQgbmFtZWQgU0laRV9GSUxURVJfU01BTEwgaW4gdGhlIFNldXJhdCBvYmplY3QsIHN0b3JpbmcgdGhlIGxvZ2ljYWwgdmVjdG9yLgoKCmBgYHtyfQpzZXVyYXRfQ1JDMVtbIlNJWkVfRklMVEVSX1NNQUxMIl1dIDwtIHNldXJhdF9DUkMxJGNlbGxfYXJlYSA+IHF1YW50aWxlKHNldXJhdCRjZWxsX2FyZWEsIC4wMSkKYGBgCgpOb3cgd2UgY2FuIHVzZSAqSW1hZ2VEaW1QbG90KiB0byB2aXN1YWxpc2UgdGhlIGNlbGxzIHdoaWNoIGhhdmUgYmVlbiBmbGFnZ2VkIGZvciByZW1vdmFsLgoKV2UgY2FuIHNlZSB0aGF0IHRoZXNlIGFyZSBtb3JlIHNjYXR0ZXJlZCB0aHJvdWdob3V0IHRoZSB0aXNzdWUgLSBidXQgdGhlcmUgbWF5IGJlIG1vcmUgaW4gdGhlIGZvbGxpY3VsYXIgcmVnaW9ucy4gCgoqKkhvdyBkbyBkaWZmZXJlbnQgdGhyZXNob2xkcyBiZWhhdmU/IElzIHRoZXJlIGEgbW9yZSBhcHByb3ByaWF0ZSBvbmUgdG8gdXNlPyBJcyBhbnkgbmVjZXNzYXJ5IGF0IGFsbD8qKgoKYGBge3J9CkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMSwgZ3JvdXAuYnk9IlNJWkVfRklMVEVSX1NNQUxMIikKYGBgCldlIGNhbiBjaGVjayBob3cgdGhlc2UgdmFsdWVzIGNvcnJlbGF0ZSB3aXRoIGdlbmUgZGV0ZWN0aW9uIHJhdGUuIAoKSWYgd2UgZmlsdGVyIG91dCBzbWFsbCBjZWxscywgd2Ugd2lsbCByZW1vdmUgY2VsbHMgd2l0aCBsb3cgbnVtYmVycyBvZiBnZW5lcyBkZXRlY3RlZC4gCgpJZiB3ZSBmaWx0ZXIgb3V0IGxhcmdlIGNlbGxzLCB0aGlzIGlzIG5vdCB0aGF0IGJpYXNlZCB0b3dhcmRzIG92ZXJseSBsYXJnZSBjb3VudHMsIGFzIHdlIHNhdyBiZWZvcmUuCgoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTd9CnAxIDwtIFZsblBsb3Qoc2V1cmF0X0NSQzEsICJuRmVhdHVyZV9YRU5JVU0iLCBncm91cC5ieSA9ICJTSVpFX0ZJTFRFUl9TTUFMTCIsIHB0LnNpemUgPSAuMSwgYWxwaGEgPSAuNSkgKyBsYWJzKHRpdGxlPSJTbWFsbCBDZWxsIEZpbHRlciIpCnAyIDwtIFZsblBsb3Qoc2V1cmF0X0NSQzEsICJuRmVhdHVyZV9YRU5JVU0iLCBncm91cC5ieSA9ICJTSVpFX0ZJTFRFUl9MQVJHRSIsIHB0LnNpemUgPSAuMSwgYWxwaGEgPSAuNSkrIGxhYnModGl0bGU9IkxhcmdlIENlbGwgRmlsdGVyIikKCnAxICsgcDIKYGBgCkFkanVzdGluZyB0aGUgdGhyZXNob2xkIGZvciB3aGF0IGlzIGNvbnNpZGVyZWQgYSAic21hbGwgY2VsbCIgY2FuIGhhdmUgc2lnbmlmaWNhbnQgaW1wbGljYXRpb25zIGZvciB5b3VyIGFuYWx5c2lzLCBlc3BlY2lhbGx5IGluIGFyZWFzIHdpdGggc3BlY2lmaWMgY2VsbCB0eXBlcyBzdWNoIGFzIFQtY2VsbHMsIHdoaWNoIGFyZSBzbWFsbCBhbmQgZGVuc2VseSBwYWNrZWQgaW4gZm9sbGljdWxhciByZWdpb25zLiBUaGlzIGV4YW1wbGUgZGVtb25zdHJhdGVzIGhvdyBjaGFuZ2luZyB0aGUgdGhyZXNob2xkIHRvIHRoZSAxMHRoIHBlcmNlbnRpbGUgYWZmZWN0cyB0aGUgZmlsdGVyaW5nLiBJbiB0aGlzIGNhc2UsIHdlIHdvdWxkIHByb2JhYmx5IGZpbHRlciBvdXQgYSBsb3Qgb2YgZ29vZCBjZWxscyB0aGF0IHdlIGRvbid0IHdhbnQgdG8gbG9zZSEgU28sIGJlIGNhcmVmdWwgd2hlbiBsb29raW5nIGF0IHRoZXNlIHR5cGVzIG9mIFFDIG1ldHJpY3MhCgoKYGBge3J9CnNldXJhdF9DUkMxW1siU0laRV9GSUxURVJfU01BTEwiXV0gPC0gc2V1cmF0X0NSQzEkY2VsbF9hcmVhID4gcXVhbnRpbGUoc2V1cmF0X0NSQzEkY2VsbF9hcmVhLCAuMSkKYGBgCgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMxLCBncm91cC5ieT0iU0laRV9GSUxURVJfU01BTEwiKQpgYGAKTGV0cyBzZXQgdGhpcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCAxJSB0aHJlc2hvbGQuCmBgYHtyfQpzZXVyYXRfQ1JDMVtbIlNJWkVfRklMVEVSX1NNQUxMIl1dIDwtIHNldXJhdF9DUkMxJGNlbGxfYXJlYSA+IHF1YW50aWxlKHNldXJhdF9DUkMxJGNlbGxfYXJlYSwgLjAxKQpgYGAKCgpUaGUgbW9zdCBpbXBvcnRhbnQgZmlsdGVyIGlzIHRoZSBvdmVyYWxsIHRyYW5zY3JpcHQgZGV0ZWN0aW9uLiBFbXB0eSBjZWxscyBvciBjZWxscyB3aXRoIHZlcnkgbG93IHRyYW5zY3JpcHQgY291bnQgY2Fubm90IGJlIHRha2VuIGZvcndhcmQgZm9yIGNsdXN0ZXJpbmcgYW5hbHlzaXMgYW5kIGl0IGlzIGV4dHJlbWVseSBkaWZmaWN1bHQgdG8gaWRlbnRpZnkgd2hhdCB0aGV5IG1heSBiZS4gSGVyZSwgd2Ugc2V0IGEgdGhyZXNob2xkIG9mIG1pbmltdW0gMTUgdHJhbnNjcmlwdHMuIFRoaXMgc2VlbXMgcXVpdGUgbG93IC0gZm9yIGRhdGEgZnJvbSAqaW4gc2l0dSogcGxhdGZvcm1zIHdpdGggbG93IG5vaXNlIChYZW5pdW0sIE1lcmZpc2gsIE1lcnNjb3BlKSwgdGhpcyBpcyBnZW5lcmFsbHkgZW5vdWdoIHRvIGNsdXN0ZXIgYW5kIGlkZW50aWZ5IGNlbGwgdHlwZXMuIElmIHlvdXIgZGF0YSBoYXMgbW9yZSBub2lzZSAoZS5nLiBDb3NNeCksIGEgaGlnaGVyIHRocmVzaG9sZCBpcyBtb3JlIGFwcHJvcHJpYXRlLgoKCipzZXVyYXQkbkNvdW50X1hFTklVTSA+PSAxNSo6IENvbXBhcmVzIGVhY2ggY2VsbCdzIHRyYW5zY3JpcHQgY291bnQgdG8gdGhlIHRocmVzaG9sZCBvZiAxNS4gVGhlIHJlc3VsdCBpcyBhIGxvZ2ljYWwgdmVjdG9yIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBUUlVFIGlmIHRoZSBjb3JyZXNwb25kaW5nIGNlbGwgaGFzIGF0IGxlYXN0IDE1IHRyYW5zY3JpcHRzIGFuZCBGQUxTRSBvdGhlcndpc2UuCipzZXVyYXQkVFJBTlNDUklQVF9GSUxURVIqOiBDcmVhdGVzIGEgbmV3IG1ldGFkYXRhIGZpZWxkIG5hbWVkIFRSQU5TQ1JJUFRfRklMVEVSIGluIHRoZSBTZXVyYXQgb2JqZWN0LCBzdG9yaW5nIHRoZSBsb2dpY2FsIHZlY3Rvci4KCgpgYGB7cn0Kc2V1cmF0X0NSQzEkVFJBTlNDUklQVF9GSUxURVIgPC0gc2V1cmF0X0NSQzEkbkNvdW50X1hFTklVTSA+PSAxNQpgYGAKCkFuZCB3ZSBjYW4gdmlzdWFsaXNlIHRoZSBjZWxscyB0aGF0IHdlIHdvdWxkIGxvc2UuIAoKV2Ugc2VlIHRoYXQgd2UgZGlzcHJvcG9ydGlvbmF0ZWx5IHdvdWxkIGZpbHRlciBvdXQgbW9yZSBjZWxscyBmcm9tIHNvbWUgcmVnaW9ucyB0aGFuIG90aGVycy4gQXMgcG9pbnRlZCBvdXQgcHJldmlvdXNseSwgdGhpcyBpcyBsaWtlbHkgZHVlIHRvIGEgY29tYmluYXRpb24gb2YgZ2VuZSBwYW5lbCBjb3ZlcmFnZSBpbiBzb21lIHJlZ2lvbnMgYW5kIHZlcnkgc21hbGwgY2VsbHMgaW4gZGVuc2VseSBwYWNrZWQgcmVnaW9ucyBsaWtlIGZvbGxpY2xlcy4KCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzEsIGdyb3VwLmJ5PSJUUkFOU0NSSVBUX0ZJTFRFUiIpCmBgYApGaW5hbGx5LCB2aXN1YWxpemluZyB0aGUgY291bnRzIG9mIG5lZ2F0aXZlIGNvbnRyb2wgY29kZXdvcmRzLCBuZWdhdGl2ZSBjb250cm9sIHByb2JlcywgYW5kIHVuYXNzaWduZWQgY29kZXdvcmRzIGhlbHBzIGlkZW50aWZ5IGFuZCB1bmRlcnN0YW5kIHRlY2huaWNhbCBhcnRpZmFjdHMgYW5kIGJhY2tncm91bmQgbm9pc2UgaW4geW91ciBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhLgoKSGVyZSwgd2UgY2FuIHNlZSB0aGF0IGFsbCBjb250cm9sIHByb2JlcyBhbmQgY29kZXdvcmRzIHByb2R1Y2UgeWllbGQgdmVyeSBsaXR0bGUgc2lnbmFsLCBzdWdnZXN0aW5nIG91ciBkYXRhIGlzIGdvb2QgcXVhbGl0eSEgCgpJbiBzb21lIGNhc2VzLCBoaWdoIGFtb3VudCBvZiBhdXRvZmxvdXJlc2NlbmNlIGlzIHRoZSBjZWxscy90aXNzdWUgY2FuIHNvbWV0aW1lcyBnZW5lcmF0ZSBmYWxzZSBwb3NpdGl2ZSBzaWduYWwgYW5kIHRoaXMgc2hvdWxkIGJlIGZpbHRlcmVkIG91dC4gCgpgYGB7ciBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03fQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCAibkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuQ29kZXdvcmQiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIm5Db3VudF9OZWdhdGl2ZS5Db250cm9sLlByb2JlIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzEsICJuQ291bnRfVW5hc3NpZ25lZC5Db2Rld29yZCIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCkFsdGhvdWdoIHRoZSBuZWdhdGl2ZSBjb250cm9sIHNpZ25hbCBpcyBsb3csIHdlIGNhbiBub25ldGhlbGVzcyBjcmVhdGUgYSBmaWx0ZXIgdG8gcmVtb3ZlIGNlbGxzIHdoaWNoIGhhdmUgYW55LCBhbHRob3VnaCBpbiB0aGlzIGNhc2UgaXQgaXMgcHJvYmFibHkgdW5uZWNlc3NhcnkuCgpgYGB7cn0Kc2V1cmF0X0NSQzEkUFJPQkVfRklMVEVSIDwtIHNldXJhdF9DUkMxJG5Db3VudF9VbmFzc2lnbmVkLkNvZGV3b3JkID09IDAgJgogICAgICAgICAgICAgICAgICAgICAgIHNldXJhdF9DUkMxJG5Db3VudF9OZWdhdGl2ZS5Db250cm9sLkNvZGV3b3JkID09IDAgJgogICAgICAgICAgICAgICAgICAgICAgIHNldXJhdF9DUkMxJG5Db3VudF9OZWdhdGl2ZS5Db250cm9sLlByb2JlID09IDAKCgpgYGAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzEsIGdyb3VwLmJ5PSJQUk9CRV9GSUxURVIiKQpgYGAKRmluYWxseSwgd2UgY2FuIHN1YnNldCB0aGUgc2V1cmF0IG9iamVjdCBiYXNlZCBvbiBhbnkvYWxsIG9mIHRoZSBmaWx0ZXJzIHdlIGhhdmUgY3JlYXRlZCBlYXJsaWVyLiAKCkJ5IGNvbWJpbmluZyBwcm9iZSwgc2l6ZSwgYW5kIHRyYW5zY3JpcHQgZmlsdGVycywgeW91IGNhbiByZXRhaW4gb25seSB0aGUgY2VsbHMgdGhhdCBtZWV0IGFsbCBxdWFsaXR5IGNyaXRlcmlhLCByZWR1Y2luZyB0aGUgaW1wYWN0IG9mIHRlY2huaWNhbCBhcnRpZmFjdHMgYW5kIG5vaXNlIG9uIHlvdXIgYW5hbHlzaXMuCgpgYGB7cn0Kc2V1cmF0X0NSQzEgPC0gc3Vic2V0KHNldXJhdF9DUkMxLCBQUk9CRV9GSUxURVIgJiBTSVpFX0ZJTFRFUl9MQVJHRSAmIFNJWkVfRklMVEVSX1NNQUxMICYgVFJBTlNDUklQVF9GSUxURVIpCmBgYApMZXRzIGV4YW1pbmUgdGhlIGNsZWFuZWQgdXAgb2JqZWN0IC0gd2UgaGF2ZSBsb3N0IGEgZmV3IHRob3VzYW5kIGNlbGxzIGZyb20gdGhlIGFuYWx5c2lzLiAKYGBge3J9CnNldXJhdF9DUkMxCgpgYGAKKipEYXRhIE5vcm1hbGlzYXRpb24qKgoKVGhlICpTQ1RyYW5zZm9ybSogZnVuY3Rpb24gaW4gU2V1cmF0IGlzIHVzZWQgZm9yIG5vcm1hbGl6aW5nIHNpbmdsZS1jZWxsIFJOQS1zZXEgYW5kIHNwYXRpYWwgdHJhbnNjcmlwdG9taWNzIGRhdGEuIFRoaXMgbWV0aG9kIG1vZGVscyB0aGUgZ2VuZSBleHByZXNzaW9uIGNvdW50cyB1c2luZyBhIHJlZ3VsYXJpemVkIG5lZ2F0aXZlIGJpbm9taWFsIHJlZ3Jlc3Npb24gYW5kIHJlbW92ZXMgdGVjaG5pY2FsIG5vaXNlIHdoaWxlIHByZXNlcnZpbmcgYmlvbG9naWNhbCB2YXJpYWJpbGl0eS4gVGhlICpjbGlwLnJhbmdlKiBwYXJhbWV0ZXIgaXMgdXNlZCB0byBsaW1pdCB0aGUgcmFuZ2Ugb2YgdGhlIHRyYW5zZm9ybWVkIHZhbHVlcywgd2hpY2ggY2FuIGhlbHAgc3RhYmlsaXplIGRvd25zdHJlYW0gYW5hbHlzZXMgYnkgbGltaXRpbmcgdGhlIGluZmx1ZW5jZSBvZiBleHRyZW1lIHZhbHVlcy4gCgoKYGBge3J9CnNldXJhdF9DUkMxIDwtIFNDVHJhbnNmb3JtKHNldXJhdF9DUkMxLCBhc3NheSA9ICJYRU5JVU0iLCBjbGlwLnJhbmdlID0gYygtMTAsIDEwKSkKYGBgCgpQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpIGlzIGEgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHRlY2huaXF1ZSB1c2VkIHRvIGlkZW50aWZ5IHRoZSBwcmltYXJ5IGF4ZXMgb2YgdmFyaWF0aW9uIGluIGhpZ2gtZGltZW5zaW9uYWwgZGF0YS4gSW4gdGhlIGNvbnRleHQgb2Ygc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MsIFBDQSBoZWxwcyB0byByZWR1Y2UgdGhlIGNvbXBsZXhpdHkgb2YgdGhlIGRhdGEgd2hpbGUgcHJlc2VydmluZyB0aGUgbW9zdCBpbXBvcnRhbnQgcGF0dGVybnMgb2YgdmFyaWF0aW9uLiAKCgpUSVA6IElmIHlvdXIgdGFyZ2V0IHBhbmVsIGlzIHZlcnkgc21hbGwsIHlvdSBjYW4gc2tpcCB0aGlzIHN0ZXAgYW5kIGNhcnJ5IG91dCBjbHVzdGVyaW5nIGFuYWx5c2lzIGRpcmVjdGx5IG9uIGdlbmUgZXhwcmVzc2lvbi4gVGhpcyBjYW4gc29tZXRpbWVzIGhlbHAgd2l0aCBhY2hpZXZpbmcgYmV0dGVyIGNsdXN0ZXJpbmcgcmVzdWx0cy4KYGBge3J9CnNldXJhdF9DUkMxIDwtIFJ1blBDQShzZXVyYXRfQ1JDMSkKYGBgCkFzIGJlZm9yZSwgd2UgY2FuIHZpc3VhbGlzZSBob3cgbXVjaCB2YXJpYXRpb24gaXMgY2FwdHVyZWQgYnkgZWFjaCBQQy4gCgpUaGUgRWxib3dQbG90IGZ1bmN0aW9uIGhlbHBzIHRvIGRldGVybWluZSB0aGUgbnVtYmVyIG9mIHNpZ25pZmljYW50IFBDcyB0byB1c2UgZm9yIGRvd25zdHJlYW0gYW5hbHlzZXMuIFRoZSBwbG90IHR5cGljYWxseSBzaG93cyB0aGUgYW1vdW50IG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIFBDLCBhbmQgdGhlICJlbGJvdyIgcG9pbnQgaW5kaWNhdGVzIGEgbmF0dXJhbCBjdXRvZmYuCgoKYGBge3J9CkVsYm93UGxvdChzZXVyYXRfQ1JDMSwgNTApCmBgYApQbG90dGluZyB0aGUgdG9wIGdlbmVzIGNvbnRyaWJ1dGluZyB0byBhIHNwZWNpZmljIHByaW5jaXBhbCBjb21wb25lbnQgaGVscHMgaW4gdW5kZXJzdGFuZGluZyB0aGUgYmlvbG9naWNhbCBmYWN0b3JzIGRyaXZpbmcgdGhlIHZhcmlhdGlvbiBjYXB0dXJlZCBieSB0aGF0IGNvbXBvbmVudC4gVGhpcyB0eXBlIG9mIHBsb3QgaGlnaGxpZ2h0cyB0aGUgZ2VuZXMgd2l0aCB0aGUgaGlnaGVzdCBsb2FkaW5ncywgd2hpY2ggYXJlIHRoZSBtb3N0IGluZmx1ZW50aWFsIGluIHRoZSBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzLgoKYGBge3IgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9N30KUENfUGxvdHRpbmcoc2V1cmF0X0NSQzEsIGRpbV9udW1iZXIgPSAxKQpgYGAKClRoZSAqRmVhdHVyZVBsb3QqIGZ1bmN0aW9uIGluIFNldXJhdCBpcyB1c2VkIHRvIHZpc3VhbGl6ZSB0aGUgZXhwcmVzc2lvbiBvZiBhIHNwZWNpZmljIGdlbmUgYWNyb3NzIGNlbGxzIGluIGEgZ2l2ZW4gZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHNwYWNlIChlLmcuLCBQQ0EpLiBUaGlzIGhlbHBzIHRvIHVuZGVyc3RhbmQgaG93IHRoZSBleHByZXNzaW9uIG9mIGEgZ2VuZSB2YXJpZXMgYWNyb3NzIHRoZSBwcmluY2lwYWwgY29tcG9uZW50cy4KCmBgYHtyfQpGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIkNFQUNBTTUiLCByZWR1Y3Rpb24gPSAicGNhIikgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKQpgYGAKV2UgY2FuIGFsc28gZXhhbWluZSBob3cgdmFyaW91cyBQQ3MgYXJlIGRpc3RyaWJ1dGVkIHNwYXRpYWxseS4gCgpIZXJlLCB3ZSBjYW4gc2VlIHRoYXQgaGlnaCBQQzEgbG9hZGluZ3MgZW5yaWNoIGluIGZvbGxpY3VsYXIgc3RydWN0dXJlcyBhbmQgbG93IFBDMSBsb2FkaW5ncyBlbnJpY2ggaW4gY3J5cHQgdG9wIGNlbGxzLgpgYGB7cn0KSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIlBDXzEiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKYGBgCgpXZSBjYW4gcGxvdCB0aGUgZXhwcmVzc2lvbiBvZiBoaWdoIChvciBsb3cpIGxvYWRpbmcgZ2VuZXMgdG8gdmlzdWFsaXNlIGhvdyB0aGlzIGNvcnJlbGF0ZXMgd2l0aCBvdXIgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uLgpgYGB7cn0KSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIkNEMjQiLCBzaXplPS41KSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIkFOWEExIiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzEsICJNUzRBMSIsIHNpemU9LjUpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCAiU01PQzIiLCBzaXplPS41KSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKCnNldXJhdF9DUkMxCmBgYApOZXh0LCB3ZSB3aWxsIHVzZSB0aGUgcmVkdWNlZCBkaW1lbnNpb25hbGl0eSBkYXRhIGZvciBjbHVzdGVyaW5nIGFuZCBjbHVzdGVyIHZpc3VhbGlzYXRpb24uIAoKKlJ1blVNQVAqOiBQZXJmb3JtIFVuaWZvcm0gTWFuaWZvbGQgQXBwcm94aW1hdGlvbiBhbmQgUHJvamVjdGlvbiAoVU1BUCkgdG8gcmVkdWNlIHRoZSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgZGF0YSBmb3IgdmlzdWFsaXphdGlvbi4gVGhlIFVNQVAgcGxvdCByZWR1Y2VzIHRoZSBoaWdoLWRpbWVuc2lvbmFsIGRhdGEgdG8gdHdvIGRpbWVuc2lvbnMsIHByZXNlcnZpbmcgdGhlIGxvY2FsIGFuZCBnbG9iYWwgc3RydWN0dXJlIG9mIHRoZSBkYXRhIGZvciB2aXN1YWxpemF0aW9uLiBDZWxscyB0aGF0IGFyZSBjbG9zZSB0b2dldGhlciBpbiB0aGUgVU1BUCBwbG90IGFyZSBzaW1pbGFyIGluIHRoZWlyIGdlbmUgZXhwcmVzc2lvbiBwcm9maWxlcy4KKnNldXJhdCo6IFRoZSBTZXVyYXQgb2JqZWN0LgoqZGltcyA9IDE6MjAqOiBTcGVjaWZpZXMgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzIHRvIHVzZSBmb3IgVU1BUC4KCipGaW5kTmVpZ2hib3JzKjogRmluZGluZyBuZWFyZXN0IG5laWdoYm9ycyBoZWxwcyB0byBpZGVudGlmeSBjZWxscyB0aGF0IGFyZSBzaW1pbGFyIGJhc2VkIG9uIHRoZWlyIFBDQSBzY29yZXMsIHdoaWNoIGlzIHVzZWQgZm9yIGNsdXN0ZXJpbmcuCipzZXVyYXQqOiBUaGUgU2V1cmF0IG9iamVjdC4KKnJlZHVjdGlvbiA9ICJwY2EiKjogU3BlY2lmaWVzIHRoYXQgdGhlIFBDQSBzcGFjZSBzaG91bGQgYmUgdXNlZCBmb3IgZmluZGluZyBuZWlnaGJvcnMuCipkaW1zID0gMToyMCo6IFNwZWNpZmllcyB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgdG8gdXNlIGZvciBpZGVudGlmeWluZyBuZWlnaGJvcnMuCgoqRmluZENsdXN0ZXJzKjogQ2x1c3RlcmluZyBpZGVudGlmaWVzIGRpc3RpbmN0IGdyb3VwcyBvZiBjZWxscyB3aXRoIHNpbWlsYXIgZ2VuZSBleHByZXNzaW9uIHBhdHRlcm5zLiBUaGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgY29udHJvbHMgdGhlIGdyYW51bGFyaXR5IG9mIHRoZSBjbHVzdGVyaW5nLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QuCipyZXNvbHV0aW9uID0gMC43KjogU2V0cyB0aGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgZm9yIGNsdXN0ZXJpbmcuIEhpZ2hlciB2YWx1ZXMgbGVhZCB0byBtb3JlIGNsdXN0ZXJzLCB3aGlsZSBsb3dlciB2YWx1ZXMgbGVhZCB0byBmZXdlciBjbHVzdGVycy4KCmBgYHtyfQpzZXVyYXRfQ1JDMSA8LSBSdW5VTUFQKHNldXJhdF9DUkMxLCBkaW1zID0gMToyMCkKc2V1cmF0X0NSQzEgPC0gRmluZE5laWdoYm9ycyhzZXVyYXRfQ1JDMSwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjIwKQpzZXVyYXRfQ1JDMSA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzEsIHJlc29sdXRpb24gPSAwLjIpCmBgYAoKTmV4dCBsZXRzIHZpc3VhbGlzZSB0aGUgY2x1c3RlcnMgLSBmaXJzdGx5LCBiYXNlZCBvbiB0cmFuc2NyaXB0b21lIGVtYmVkZGluZy4KCipEaW1QbG90KjogQ3JlYXRlcyBhIHNjYXR0ZXIgcGxvdCBvZiBjZWxscyBpbiBhIHJlZHVjZWQtZGltZW5zaW9uYWwgc3BhY2UsIGJ5IGRlZmF1bHQgbm93IHVzaW5nIFVNQVAgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QgY29udGFpbmluZyB0aGUgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHJlc3VsdHMgYW5kIGNsdXN0ZXIgYXNzaWdubWVudHMuCipsYWJlbCA9IFRSVUUqOiBBZGRzIGNsdXN0ZXIgbGFiZWxzIHRvIHRoZSBwbG90LgoqcmVwZWwgPSBUUlVFKjogUmVwZWxzIHRoZSBsYWJlbHMgdG8gYXZvaWQgb3ZlcmxhcHBpbmcsIG1ha2luZyB0aGUgcGxvdCBjbGVhcmVyLgoKCmBgYHtyfQpEaW1QbG90KHNldXJhdF9DUkMxLCBsYWJlbD1ULCByZXBlbD1UKQpgYGAKQW5kIG5vdyBsZXRzIHBsb3QgdGhlIGNsdXN0ZXJzIGluIHRpc3N1ZSBzcGFjZS4gCgpXZSBjYW4gc2VlIHRoYXQgb3VyIGNsdXN0ZXJzIGhhdmUgcXVpdGUgbmljZSBjb3JyZXNwb25kZW5jZSB0byBkaXN0aW5jdCBzcGF0aWFsIHJlZ2lvbnMuCmBgYHtyfQoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMxLCBzaXplPS41LCBheGVzPVQpCmBgYApBcyBiZWZvcmUsIG5vdyB3ZSBjYW4gdXNlIFNldXJhdCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBmdW5jdGlvbnMgdG8gaWRlbnRpZnkgbWFya2VyIGdlbmVzIGZvciBzcGVjaWZpYyBjZWxsIGNsdXN0ZXJzLgoKKkZpbmRNYXJrZXJzKjogSWRlbnRpZmllcyBnZW5lcyB0aGF0IGFyZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgaW4gYSBzcGVjaWZpZWQgY2x1c3RlciBjb21wYXJlZCB0byBhbGwgb3RoZXIgY2VsbHMuCipzZXVyYXQqOiBUaGUgU2V1cmF0IG9iamVjdCBjb250YWluaW5nIHRoZSBnZW5lIGV4cHJlc3Npb24gZGF0YSBhbmQgY2x1c3RlciBpZGVudGl0aWVzLgoqaWRlbnQuMSA9ICIwIio6IFNwZWNpZmllcyB0aGUgY2x1c3RlciBvZiBpbnRlcmVzdCBmb3Igd2hpY2ggbWFya2VyIGdlbmVzIGFyZSB0byBiZSBpZGVudGlmaWVkLiBJbiB0aGlzIGNhc2UsIGNsdXN0ZXIgIjAiLgoqbWF4LmNlbGxzLnBlci5pZGVudCA9IDUwMCo6IExpbWl0cyB0aGUgbnVtYmVyIG9mIGNlbGxzIHRvIGJlIHVzZWQgZnJvbSBlYWNoIGNsdXN0ZXIgZm9yIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyB0byA1MDAuIFRoaXMgY2FuIGhlbHAgdG8gc3BlZWQgdXAgdGhlIGNvbXB1dGF0aW9uLgoKCmBgYHtyfQptYXJrZXJzIDwtIEZpbmRNYXJrZXJzKHNldXJhdF9DUkMxLCBpZGVudC4xPSIwIiwgbWF4LmNlbGxzLnBlci5pZGVudD01MDApCmBgYAoKYGBge3J9CmhlYWQobWFya2VycykKYGBgCgpXZSBjYW4gdmlzdWFsaXNlIGV4cHJlc3Npb24gb2YgY2x1c3RlciBzcGVjaWZpYyBtYXJrZXJzIHVzaW5nIGZlYXR1cmUgcGxvdHMKYGBge3J9CkZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCAiQ0QzRSIsIGxhYmVsPVQsIHJlcGVsPVQpKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoZGlyZWN0aW9uPS0xKQpGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIk1TNEExIiwgbGFiZWw9VCwgcmVwZWw9VCkrICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoZGlyZWN0aW9uPS0xKQpGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIkNFQUNBTTUiLCBsYWJlbD1ULCByZXBlbD1UKSsgc2NhbGVfY29sb3JfdmlyaWRpc19jKGRpcmVjdGlvbj0tMSkKRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzEsICJLSVQiLCBsYWJlbD1ULCByZXBlbD1UKSsgc2NhbGVfY29sb3JfdmlyaWRpc19jKGRpcmVjdGlvbj0tMSkKRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzEsICJDRDI0IiwgbGFiZWw9VCwgcmVwZWw9VCkrIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhkaXJlY3Rpb249LTEpCgpgYGAKT3IsIGFzIGluIG91ciBzZXF1ZW5jaW5nIFNUIHR1dG9yaWFsLCBkZXRlY3QgYW5kIHZpc3VhbGlzZSB0b3AgbWFya2VycyBmb3IgZXZlcnkgY2x1c3Rlci4KYGBge3J9Cm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoc2V1cmF0X0NSQzEsIG1heC5jZWxscy5wZXIuaWRlbnQgPSA1MDApCmBgYAoKYGBge3J9CmhlYWQobWFya2VycykKCmBgYAoKc2NDdXN0b21pemUgcGFja2FnZSBwcm92aWRlcyBhIGNvbnZlbmllbnQgaGVscGVyIGZ1bmN0aW9uLCAqRXh0cmFjdF9Ub3BfTWFya2VycyosIHRvIGV4dHJhY3QgdGhlIHRvcCBtYXJrZXIgZ2VuZXMgZm9yIGVhY2ggY2x1c3RlciBmcm9tIHRoZSBvdXRwdXQgb2YgKkZpbmRBbGxNYXJrZXJzKi4gVGhpcyBmdW5jdGlvbiBzaW1wbGlmaWVzIHRoZSBwcm9jZXNzIG9mIGlkZW50aWZ5aW5nIGFuZCByZXRyaWV2aW5nIHRoZSBtb3N0IHNpZ25pZmljYW50IG1hcmtlciBnZW5lcyBmb3IgYW5hbHlzaXMgYW5kIHZpc3VhbGlzYXRpb24uCgpJbiB0aGlzIGNhc2UsIHdlIGFyZSBleHRyYWN0aW5nIHRoZSB0b3AgZml2ZSBtYXJrZXJzIHBlciBjbHVzdGVyLgoKYGBge3J9CnRvcCA8LSBFeHRyYWN0X1RvcF9NYXJrZXJzKG1hcmtlcnMsIG51bV9nZW5lcyA9IDUsIG5hbWVkX3ZlY3RvciA9IEZBTFNFLCBtYWtlX3VuaXF1ZSA9IFRSVUUpCnRvcApgYGAKCipDbHVzdGVyZWRfRG90UGxvdCogZnVuY3Rpb24gZnJvbSB0aGUgKnNjQ3VzdG9taXplKiBwYWNrYWdlIHByb3ZpZGVzIGEgY29udmVuaWVudCBhbmQgdmlzdWFsbHkgYXBwZWFsaW5nIHdheSB0byBkaXNwbGF5IGV4cHJlc3Npb24gcGF0dGVybnMgb2YgdG9wIG1hcmtlciBnZW5lcyBhY3Jvc3MgY2x1c3RlcnMgdXNpbmcgYSBkb3QgcGxvdC4gVGhpcyBmdW5jdGlvbiBub3Qgb25seSBwbG90cyB0aGUgZXhwcmVzc2lvbiBkYXRhIGJ1dCBhbHNvIGNsdXN0ZXJzIHRoZSBnZW5lcyBhbmQgZ3JvdXBzIGZvciBlbmhhbmNlZCB2aXN1YWwgaW50ZXJwcmV0YXRpb24uIFRoaXMgaXMgYW4gYWx0ZXJuYXRpdmUgdG8gU2V1cmF0ICpEb3RQbG90KiBmdW5jdGlvbi4gCgoqayA9IDE4KjogRGV0ZXJtaW5lcyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGZvciB0aGUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgZ2VuZXMgdG8gZW5oYW5jZSB2aXN1YWwgc2VwYXJhdGlvbiBvZiBleHByZXNzaW9uIHBhdHRlcm5zLiAKCldlIGNhbiBzZWUgdGhhdCBtb3N0IGNsdXN0ZXJzIGhhdmUgdW5pcXVlIG1hcmtlcnMsIHdoaWNoIHN1Z2dlc3RzIHRoZSBkYXRhc2V0IGlzIG5vdCBvdmVyLWNsdXN0ZXJlZC4KCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CkNsdXN0ZXJlZF9Eb3RQbG90KHNldXJhdF9DUkMxLCBmZWF0dXJlcyA9IHRvcCwgaz0xOCwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkIikKYGBgCgoqKkFkZGl0aW9uYWwgU3BhdGlhbCBWaXN1YWxpc2F0aW9ucyoqCgpUaGUgcmVzb2x1dGlvbiBvZiAqaW4gc2l0dSogZGF0YXNldHMgaXMgdHlwaWNhbGx5IHZlcnkgaGlnaCBhbmQgc28gaXQgY2FuIGJlIGRpZmZpY3VsdCB0byB2aXN1YWxpc2UgZXZlcnl0aGluZyBpbiBvbmUgcGxvdC4gQmVsb3csIHdlIHdpbGwgZXhwbG9yZSBkaWZmZXJlbnQgdmlzdWFsaXNhdGlvbnMgdGhhdCBjYW4gaGVscCB1bnBpY2sgYW5kIHVuZGVyc3RhbmQgdGhlIGRhdGEgYSBiaXQgYmV0dGVyLiAKCgpUbyBiZXR0ZXIgdmlzdWFsaXNlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIGNsdXN0ZXJzLCBzb21ldGltZXMgaXQgY2FuIGJlIHVzZWZ1bCB0byBzdWJzZXQgb25seSBjZXJ0YWluIGdyb3VwcyB0byByZWR1Y2UgY3Jvd2RpbmcuICBIZXJlLCB3ZSBzcGVjaWZpY2FsbHkgb25seSB2aXN1YWxpc2luZyB0d28gc2VsZWN0ZWQgY2x1c3RlcnMuIAoKKldoaWNoQ2VsbHMqOiBJZGVudGlmaWVzIGNlbGxzIGJhc2VkIG9uIHNwZWNpZmllZCBjcml0ZXJpYS4KKnNldXJhdCo6IFRoZSBTZXVyYXQgb2JqZWN0LgoqZXhwcmVzc2lvbiA9IHNldXJhdF9jbHVzdGVycyAlaW4lIGMoMCwgNSkqOiBMb2dpY2FsIGV4cHJlc3Npb24gdG8gc2VsZWN0IGNlbGxzIGJlbG9uZ2luZyB0byBjbHVzdGVycyAwIGFuZCA1LgoKCioqVGhpcyB3b3JrcyB3aXRoICpJbWFnZUZlYXR1cmVQbG90KiB0b28uIFRyeSBpdCB3aXRoIHNvbWUgZ2VuZXMhKioKYGBge3J9CkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMSwgY2VsbHM9V2hpY2hDZWxscyhzZXVyYXRfQ1JDMSwgZXhwcmVzc2lvbiA9IHNldXJhdF9jbHVzdGVycyAlaW4lIGMoMCwgNSkpKQpgYGAKClNvbWV0aW1lcywgaXQgY2FuIGJlIHVzZWZ1bCB0byBjcmVhdGUgYWRkaXRpb25hbCBmaWVsZHMgb2YgdmlldyBvZiB0aGUgZGF0YSAtIGZvciBleGFtcGxlLCB6b29tcyBvZiBzcGVjaWZpYyByZWdpb25zLiAKRmlyc3QsIGxldCdzIGxvb2sgYXQgdGhlIGNvb3JkaW5hdGUgc3lzdGVtIGJ5IHBsb3R0aW5nIHRoZSBkYXRhIGFuZCB0dXJuaW5nIG9uIHRoZSBwbG90dGluZyBvZiB0aGUgYXhlcywgd2hpY2ggYXJlIG9mZiBieSBkZWZhdWx0IHRvIGNyZWF0ZSBuaWNlciBsb29raW5nIHBsb3RzLiAKClRoaXMgZ2l2ZXMgdXMgYSByb3VnaCBpZGVhIG9uIHdoZXJlIGluIHRoZSBjb29yZGluYXRlIHN5c3RlbSB0byBjcmVhdGUgYW55IHN1YnNldHMgb3Igem9vbXMgb2YgdGhlIGRhdGEuCgpGb3IgZXhhbXBsZSwgaWYgd2Ugd2FudCB0byB6b29tIGluIG9uIHRoZSBmb2xsaWNsZSBpbiB0aGUgdG9wIHJpZ2h0IGNvcm5lciwgd2UgY2FuIHNlZSB0aGF0IGl0IGxpZXMgcm91Z2hseSBiZXR3ZWVuIDQwMDAtNTAwMCBhbmQgODAwMC05MDAwIGNvb3JkaW5hdGUgcmVnaW9ucy4gCgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMxLCBheGVzID0gVCkKYGBgCgoKKipDZWxsIFR5cGUgSWRlbnRpZmljYXRpb24qKgoKWW91IGNhbiBtYW51YWxseSBhbm5vdGF0ZSB5b3VyIGNlbGwgY2x1c3RlcnMsIG9yIHlvdSBjYW4gY2xhc3NpZnkgdGhlbSB1c2luZyBhIHJlZmVyZW5jZSBzaW5nbGUtY2VsbCBkYXRhc2V0LiBUaGlzIHByb2Nlc3MgaXMgc2ltcGxlciB0aGFuIGZvciBWaXNpdW0gZGF0YSBiZWNhdXNlIG91ciBkYXRhIGlzIGF0IHRoZSBzaW5nbGUtY2VsbCBsZXZlbCwgZXN0YWJsaXNoaW5nIGEgb25lLXRvLW9uZSByZWxhdGlvbnNoaXAgd2l0aG91dCB0aGUgbmVlZCBmb3Igc3BvdCBkZWNvbnZvbHV0aW9uLgoKSG93ZXZlciwgb3VyIHRyYW5zY3JpcHRvbWUgaXMgbW9yZSBsaW1pdGVkIGhlcmUsIGFuZCBzb21lIGNlbGwgdHlwZXMgbWF5IG5vdCBiZSB3ZWxsIHJlcHJlc2VudGVkLiBBZGRpdGlvbmFsbHksIG91ciBzaW5nbGUtY2VsbCByZWZlcmVuY2UgbWlnaHQgYmUgbWlzc2luZyBzb21lIGNlbGwgdHlwZXMgdGhhdCBhcmUgbm90IHdlbGwgY2FwdHVyZWQgYnkgZHJvcGxldC1iYXNlZCB0ZWNobm9sb2dpZXMgYnV0IGFyZSBwcmVzZW50IGluIG91ciB0aXNzdWUgZGF0YS4KCkluIHRoaXMgZXhhbXBsZSwgd2Ugd2lsbCB1c2UgYSBzaW5nbGUtY2VsbCByZWZlcmVuY2UgZGF0YXNldCB0aGF0IHdlIHByZXBhcmVkIGVhcmxpZXIuCgpXZSB3aWxsIHN0YXJ0IGJ5IHJlYWRpbmcgaW4gdGhlIHNldXJhdCBSRFMgZmlsZS4KYGBge3J9CnJlZiA8LSByZWFkUkRTKCIvcHJvamVjdC9zaGFyZWQvc3BhdGlhbF9kYXRhX2NhbXAvZGF0YXNldHMvU0lOR0xFX0NFTExfUkVGRVJFTkNFUy9DT0xPTl9IQ181S19DRUxMUy5SRFMiKQpgYGAKCkV4YW1pbmUgdGhlIG9iamVjdDoKYGBge3J9CnJlZgpgYGAKQW5kIHBsb3QgdGhlIHByZS1jb21wdXRlZCBjZWxsIGNsdXN0ZXJzLiBXZSBjYW4gc2VlIHRoYXQgaGVyZSB3ZSBoYXZlIHF1aXRlIGhpZ2ggbGV2ZWwgYW5ub3RhdGlvbi4gCmBgYHtyfQpEaW1QbG90KHJlZikKYGBgCldlIHdhbnQgdG8gZXZhbHVhdGUgaG93IG11Y2ggc3RydWN0dXJhbCBpbmZvcm1hdGlvbiBpcyBsb3N0IGluIHNpbmdsZS1jZWxsIGRhdGEgd2hlbiBsaW1pdGluZyBvdXJzZWx2ZXMgdG8gdGhlIHRhcmdldGVkIGdlbmUgc2V0LiBBY2N1cmF0ZSBjbHVzdGVyIHByZWRpY3Rpb24gaXMgY2hhbGxlbmdpbmcgaWYgdGhlIGN1cnJlbnQgZ2VuZSBzZXQgZG9lcyBub3QgYWRlcXVhdGVseSBpZGVudGlmeSB0aGVtLiBUbyBkbyB0aGlzLCB3ZSB3aWxsIHF1aWNrbHkgcmUtZW1iZWRkIHRoZSBkYXRhIHVzaW5nIG9ubHkgdGhlIGdlbmVzIHByZXNlbnQgaW4gb3VyIHNwYXRpYWwgdHJhbnNjcmlwdG9taWNzIGRhdGEgYW5kIGtlZXAgdGhlIG9yaWdpbmFsIGNsdXN0ZXIgYW5ub3RhdGlvbnMgZGVyaXZlZCBmcm9tIHVuYmlhc2VkIGRhdGEuCgpJbiB0aGlzIGV4YW1wbGUsIHdlIGNhbiBvYnNlcnZlIHRoYXQgdGhlIGxpbWl0ZWQgZ2VuZSBzZXQgZG9lcyBhIHJlYXNvbmFibHkgZ29vZCBqb2IgYXQgZGlzdGluZ3Vpc2hpbmcgbWFqb3IgY2VsbCBwb3B1bGF0aW9ucy4gSG93ZXZlciwgaXQgc3RydWdnbGVzIHRvIGRpZmZlcmVudGlhdGUgYmV0d2VlbiBzaW1pbGFyIGNlbGwgdHlwZXMsIHN1Y2ggYXMgbXlvZmlicm9ibGFzdHMgYW5kIGZpYnJvYmxhc3RzLCBhcyBlZmZlY3RpdmVseSBhcyBiZWZvcmUuCgpgYGB7cn0KcmVmIDwtIFNDVHJhbnNmb3JtKHJlZiwgcmVzaWR1YWwuZmVhdHVyZXMgPXJvd25hbWVzKHNldXJhdCkpCnJlZiA8LSBSdW5QQ0EocmVmKQpyZWYgPC0gUnVuVU1BUChyZWYsIGRpbXM9MToyMCkKRGltUGxvdChyZWYsIGxhYmVsPVQsIHJlcGVsPVQpCmBgYApJZiB3ZSB2aXN1YWxpc2UgdGhlIHNwZWNpZmljaXR5IG9mIHRoZSBnZW5lIHBhbmVsIGFjcm9zcyBvdXIgc2luZ2xlIGNlbGwgcmVmZXJlbmNlIGNsdXN0ZXJzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHBhbmVsIGNvdmVyYWdlIGlzIG1haW5seSBjb25jZW50cmF0ZWQgYWNyb3NzIGVwaXRoZWxpYWwgY2VsbHMgYW5kIFQtQ2VsbHMgYW5kIG90aGVyIGltbXVuZSBjZWxscywgd2l0aCBmZXcgc3BlY2lmaWMgbWFya2VycyBleHByZXNzZWQgYnkgc3Ryb21hbCBjZWxscy4gCmBgYHtyfQpwcyA8LSBBZ2dyZWdhdGVFeHByZXNzaW9uKHJlZiwgZmVhdHVyZXMgPSByb3duYW1lcyhzZXVyYXQpLCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLCBhc3NheXM9IlJOQSIsIHJldHVybi5zZXVyYXQgPSBUKQpwcyA8LSBTY2FsZURhdGEocHMsIGZlYXR1cmVzPXJvd25hbWVzKHBzKSkKcGhlYXRtYXAoTGF5ZXJEYXRhKHBzLCBsYXllcj0ic2NhbGUuZGF0YSIpLCBzaG93X3Jvd25hbWVzID0gRikKYGBgCgoKTmV4dCwgd2UgY2FuIHVzZSB0aGUgc3RhbmRhcmQgU2V1cmF0IGludGVncmF0aW9uIGFuZCBjcm9zcy1jbGFzc2lmaWNhdGlvbiB3b3JrZmxvdyB0byB0cmFuc2ZlciBzaW5nbGUtY2VsbCBkZXJpdmVkIGxhYmVscyB0byBvdXIgc3BhdGlhbCBvYmplY3QuCgpCcmllZmx5LCB0aGUgZmlyc3QgZnVuY3Rpb24gaWRlbnRpZmllcyBhbmNob3JzIGJldHdlZW4gdGhlIHJlZmVyZW5jZSBzaW5nbGUtY2VsbCBkYXRhc2V0IChyZWYpIGFuZCB0aGUgcXVlcnkgc3BhdGlhbCBkYXRhc2V0IChzZXVyYXQpLiBBbmNob3JzIGFyZSBwYWlycyBvZiBjZWxscyB0aGF0IGFyZSBjb25zaWRlcmVkIHNpbWlsYXIgYmV0d2VlbiB0aGUgZGF0YXNldHMuIFRoZSAqbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiU0NUIiogc3BlY2lmaWVzIHRoYXQgKlNDVHJhbnNmb3JtKiBub3JtYWxpemF0aW9uIHNob3VsZCBiZSB1c2VkLgoKVGhlIHNlY29uZCBzdGVwIHRyYW5zZmVycyB0aGUgY2VsbCB0eXBlIGxhYmVscyBmcm9tIHRoZSByZWZlcmVuY2UgZGF0YXNldCB0byB0aGUgcXVlcnkgZGF0YXNldC4gVGhlIGFuY2hvcnNldCBhcmd1bWVudCBzcGVjaWZpZXMgdGhlIGFuY2hvcnMgZm91bmQgaW4gdGhlIHByZXZpb3VzIHN0ZXAuIFRoZSAqcmVmZGF0YSA9IHJlZiRDZWxsVHlwZSogYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBjZWxsIHR5cGUgbGFiZWxzIGZyb20gdGhlIHJlZmVyZW5jZSBkYXRhc2V0IHRvIGJlIHRyYW5zZmVycmVkLiBUaGUgKnByZWRpY3Rpb24uYXNzYXkgPSBUUlVFKiBhcmd1bWVudCBpbmRpY2F0ZXMgdGhhdCB0aGUgdHJhbnNmZXJyZWQgbGFiZWxzIHNob3VsZCBiZSBzdG9yZWQgaW4gYSBuZXcgYXNzYXkgaW4gdGhlIHF1ZXJ5IGRhdGFzZXQuIFRoZSAqd2VpZ2h0LnJlZHVjdGlvbiA9IHNldXJhdFtbInBjYSJdXSogYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdG8gYmUgdXNlZCBmb3Igd2VpZ2h0aW5nIHRoZSB0cmFuc2ZlciwgYW5kICpkaW1zID0gMTozMCogc3BlY2lmaWVzIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB0byB1c2UuCgoKYGBge3J9CmFuY2hvcnMgPC0gRmluZFRyYW5zZmVyQW5jaG9ycyhyZWZlcmVuY2UgPSByZWYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXRfQ1JDMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJTQ1QiKQoKc2V1cmF0X0NSQzEgPC0gVHJhbnNmZXJEYXRhKGFuY2hvcnNldCA9IGFuY2hvcnMsIAogICAgICAgICAgICAgICAgICAgICAgIHJlZmRhdGEgPSByZWYkQ2VsbFR5cGUsIAogICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3Rpb24uYXNzYXkgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgIHdlaWdodC5yZWR1Y3Rpb24gPSBzZXVyYXRfQ1JDMVtbInBjYSJdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXRfQ1JDMSwgCiAgICAgICAgICAgICAgICAgICAgICAgZGltcz0xOjMwKQoKYGBgCgpVbmZvcnR1bmF0ZWx5LCB0aGUgcHJlZGljdGVkIGxhYmVscyBhbmQgc3BhdGlhbCBjbHVzdGVycyBkbyBub3QgY29ycmVzcG9uZCBjbGVhcmx5IGluIGFsbCBjYXNlcy4gVGhpcyBkaXNjcmVwYW5jeSBpcyBwYXJ0aWN1bGFybHkgZXZpZGVudCBpbiB0aGUgbWlkZGxlIHJlZ2lvbnMgb2YgdGhlIFVNQVAsIHdoZXJlIG1hbnkgY2VsbHMgYXJlIHByZWRpY3RlZCBhcyBlcGl0aGVsaWFsIGNlbGxzIC0gcHJvYmFibHkgaW5jb3JyZWN0bHkhCgpIb3cgdG8gaW1wcm92ZSB0aGlzPwoKKipFbnN1cmUgR29vZCBSZXByZXNlbnRhdGlvbiBvZiBDZWxsIFR5cGUgTWFya2VycyBpbiAqaW4gc2l0dSogVGFyZ2V0IFBhbmVsKioKTW9zdCBjcml0aWNhbGx5LCBiZWZvcmUgdW5kZXJ0YWtpbmcgYW55IGV4cGVyaW1lbnRzIHlvdSB3YW50IHRvIGVuc3VyZSB0aGF0IHRoZXJlIGlzIGdvb2QgcmVwcmVzZW50YXRpb24gb2YgYWxsIGNlbGwgdHlwZXMgaW4geW91ciB0YXJnZXQgcGFuZWwgLSBpbiB0aGlzIGNhc2UsIHRoZXJlIGlzIG5vdCBtdWNoIHRvIGJlIGRvbmUgYXMgdGhlIGRhdGEgaGFzIGFscmVhZHkgYmVlbiBnZW5lcmF0ZWQuIAoKKipSZXZpZXcgYW5kIFJlZmluZSBSZWZlcmVuY2UgRGF0YToqKgpFbnN1cmUgdGhhdCB0aGUgcmVmZXJlbmNlIHNpbmdsZS1jZWxsIGRhdGFzZXQgaXMgY29tcHJlaGVuc2l2ZSBhbmQgYWNjdXJhdGVseSBhbm5vdGF0ZWQuIElmIGNlcnRhaW4gY2VsbCB0eXBlcyBhcmUgbm90IHdlbGwgcmVwcmVzZW50ZWQgb3IgYW5ub3RhdGVkIGluIHRoZSByZWZlcmVuY2UgZGF0YXNldCwgaXQgY2FuIGxlYWQgdG8gbWlzY2xhc3NpZmljYXRpb24uCgoqKkluY3JlYXNlIHRoZSBOdW1iZXIgb2YgRGltZW5zaW9uczoqKgpJbmNyZWFzaW5nIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB1c2VkIGluIHRoZSBVTUFQIGFuZCBQQ0Egc3RlcHMgbWlnaHQgY2FwdHVyZSBtb3JlIHZhcmlhbmNlIGluIHRoZSBkYXRhLCBsZWFkaW5nIHRvIGJldHRlciBsYWJlbCB0cmFuc2Zlci4KCioqRmlsdGVyIGFuZCBQcmVwcm9jZXNzIERhdGE6KioKRmlsdGVyaW5nIG91dCBsb3ctcXVhbGl0eSBjZWxscyBvciBnZW5lcyBhbmQgcGVyZm9ybWluZyBhZGRpdGlvbmFsIHByZXByb2Nlc3Npbmcgc3RlcHMgY2FuIGVuaGFuY2UgdGhlIGFjY3VyYWN5IG9mIHRoZSB0cmFuc2ZlciBhbmNob3JzIGFuZCwgY29uc2VxdWVudGx5LCB0aGUgbGFiZWwgcHJlZGljdGlvbnMuIAoKKipNYW51YWxseSBBbm5vdGF0ZSBvciBDb3JyZWN0IFByZWRpY3Rpb25zOioqCkluIGNhc2VzIHdoZXJlIGF1dG9tYXRpYyBsYWJlbCB0cmFuc2ZlciBpcyBpbnN1ZmZpY2llbnQsIGNvbnNpZGVyIG1hbnVhbGx5IGFubm90YXRpbmcgb3IgY29ycmVjdGluZyB0aGUgcHJlZGljdGlvbnMgZm9yIGNyaXRpY2FsIHJlZ2lvbnMgdG8gZW5zdXJlIGFjY3VyYWN5LgoKCmBgYHtyfQpEaW1QbG90KHNldXJhdF9DUkMxLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWQiKQoKCmBgYApBcyBiZWZvcmUsIHdlIGNhbiBhbHNvIHZpc3VhbGlzZSB0aGUgcHJlZGljdGVkIGNlbGwgbGFiZWxzIGluIHRpc3N1ZSBzcGFjZS4KYGBge3J9CmxpYnJhcnkoc2NhbGVzKQoKCgpjZWxsX2NvbG91cnMgPC0gYygiI0Y4NzY2RCIsICIjREI4RTAwIiwgIiNBRUEyMDAiLCAiIzY0QjIwMCIsICIjMDBCRDVDIiwgIiMwMEMxQTciLCAKCiAgICAgICAgICAgICAgICAgICIjMDBCQURFIiwgIiMwMEE2RkYiLCAiI0IzODVGRiIsICIjRUY2N0VCIiwgIiNGRjYzQjYiKQoKbmFtZXMoY2VsbF9jb2xvdXJzKSAgPC0gYygiRXBpdGhlbGl1bSIsICJGaWJyb2JsYXN0cyIsICJULUNlbGxzIiwgICJNeW9maWJyb2JsYXN0cyIsICJNYWNyb3BoYWdlcyIsICJHbGlhIiwgIkVuZG90aGVsaXVtIiwgIlRlbG9jeXRlcyIsICJQbGFzbWEiLCAiQi1DZWxscyIsICJQZXJpY3l0ZXMiKQogCkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMSwgY29scyA9IGNlbGxfY29sb3VycywgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkIikKIAojIHB1bGwgb3V0IHRoZSBwcm9wb3J0aW9uIG9mIGVhaGMgY2VsbCB0eXBlIGZyb20gdGhlIG9iamVjdCAKCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicnBvbGljYXN0cm8vc2NQcm9wb3J0aW9uVGVzdCIpCgpgYGAKSW4gbGluZSB3aXRoIG5vbi1zcGVjaWZpYyBwcmVkaWN0aW9ucywgd2UgY2FuIGFsc28gc2VlIHRoYXQgdGhlIHByZWRpY3Rpb24gc2NvcmUgYWNyb3NzIHRoZXNlIGFyZWFzIGlzIGxvd2VyLiAKCk91dHNpZGUgb2Ygc3Ryb21hbCBjZWxscywgd2UgY2FuIGFsc28gc2VlIHRoYXQgcHJlZGljdGlvbiBwcm9iYWJpbGl0eSBjYW4gYmUgbG93IGluIGNlbGxzIHRoYXQgZW1iZWRkICJiZXR3ZWVuIiBjbHVzdGVycywgZm9yIGV4YW1wbGUgYmV0d2VlbiBjb3JlIFQtQ2VsbHMgYW5kIEItQ2VsbHMsIHR3byBwb3B1bGF0aW9ucyB0aGF0IHNob3VsZCBiZSBkaXN0aW5jdC4gCgpUaGlzIGlzIG9mdGVuIHRoZSBjYXNlIHdoZXJlIGNlbGwgc2VnbWVudGF0aW9uIGlzIGltcGVyZmVjdCBhbmQgcGFydGl0aW9ucyB0cmFuc2NyaXB0cyBpbiBzdWNoIGEgd2F5IHRoYXQgaXQgZ2VuZXJhdGVzICJhcnRpZmljaWFsIiBkb3VibGV0cyBieSBwdWxsaW5nIGluIHRyYW5zY3JpcHRzIGZyb20gYW4gYWRqYWNlbnQgY2VsbC4gCmBgYHtyfQpGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgInByZWRpY3RlZC5pZC5zY29yZSIpCmBgYApGb3IgZXhhbXBsZSwgaWYgd2UgdmlzdWFsaXNlIHRoZSBsaW5lYWdlIG1hcmtlcnMgZm9yIFQtQ2VsbHMgYW5kIEItQ2VsbHMsIHdlIGNhbiBzZWUgdGhhdCB0aGV5IGFyZSBvZnRlbiAiY28tZXhwcmVzc2VkIiBpbiB0aGUgc2FtZSBjZWxscyB3aGVuIGJpb2xvZ2ljYWxseSwgdGhleSBzaG91bGQgbm90IGJlLiAKClRoZSAqRmVhdHVyZVNjYXR0ZXIqIGZ1bmN0aW9uIGluIFNldXJhdCBpcyB1c2VkIHRvIGNyZWF0ZSBhIHNjYXR0ZXIgcGxvdCBzaG93aW5nIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgdHdvIGdlbmVzIGFjcm9zcyBhbGwgY2VsbHMuIFRoaXMgdmlzdWFsaXphdGlvbiBoZWxwcyB0byBpZGVudGlmeSBwb3RlbnRpYWwgY29ycmVsYXRpb25zIG9yIHBhdHRlcm5zIGJldHdlZW4gdGhlIHR3byBnZW5lcy4KCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0KRmVhdHVyZVNjYXR0ZXIoc2V1cmF0X0NSQzEsICJNUzRBMSIsICJDRDNEIiwgaml0dGVyPVQpCkZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCBjKCJNUzRBMSIsICJDRDNEIikpCkZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCBjKCJNUzRBMSIsICJDRDI0IikpCkZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCBjKCJDRDI0IiwgIkFOWEExIikpCgpgYGAKCgogI2ZvciB0aGUgc2FrZSBvZiB0aGUgdHV0b3JpYWwgd2Ugc2tpcHBlZCBzZWdtZW50YWl0b24sIG1vdmVkIHN0cmVpZ2h0IHRvIG5lYXJlc3QgbmVpZ2hib3VyLgogCgoKVG8gaW1wcm92ZSB0aGVzZSBhcnRlZmFjdHMsIHdlIGNhbiB0cnkgYWx0ZXJuYXRpdmUgY2VsbCBzZWdtZW50YXRpb24gYWxnb3JpdGhtcy4gV2hhdCB3b3JrcyBiZXN0IGlzIHZlcnkgdGlzc3VlIGRlcGVuZGFudCBhbmQgdGhlcmUncyBubyBlYXN5IG9uZSBzdG9wIHNvbHV0aW9uIHRvIHRoaXMuIENlbGwgc2VnbWVudGF0aW9uIGFsZ29yaXRobXMgY2FuIGJlIGRpdmlkZWQgaW50byBhIGZldyBncm91cHMuIAoKKipOdWNsZWktYmFzZWQgU2VnbWVudGF0aW9uKiogYWxnb3JpdGhtcyBwcmltYXJpbHkgZm9jdXMgb24gaWRlbnRpZnlpbmcgY2VsbCBudWNsZWksIHdoaWNoIGFyZSB1c3VhbGx5IG1vcmUgZGlzdGluY3QgYW5kIGVhc2llciB0byBkZXRlY3QgdGhhbiB0aGUgY2VsbCBib3VuZGFyaWVzLiBPbmNlIHRoZSBudWNsZWkgYXJlIGlkZW50aWZpZWQsIHRoZSBjZWxsIGJvdW5kYXJpZXMgYXJlIGluZmVycmVkIGJ5IGV4cGFuZGluZyBhcm91bmQgdGhlIG51Y2xlaS4gVGhpcyBhcHByb2FjaCB3b3JrcyB3ZWxsIGluIHRpc3N1ZXMgd2hlcmUgdGhlIG51Y2xlaSBhcmUgY2xlYXJseSB2aXNpYmxlIGFuZCBkaXN0aW5jdCBhbmQgaW4gZWFybHkgdmVyc2lvbnMgb2YgbWFueSBpbiBzaXR1IHBsYXRmb3Jtcywgd2VyZSB0aGUgb25seSBhdmFpbGFibGUgbWV0aG9kcyBkdWUgdG8gb25seSB1c2luZyBEQVBJIHN0YWluLgoKKipDZWxsIEJvdW5kYXJ5LUJhc2VkIFNlZ21lbnRhdGlvbioqIGFsZ29yaXRobXMgKGUuZy4gQ2VsbHBvc2UpIGRpcmVjdGx5IHNlZ21lbnRzIGNlbGxzIGJ5IGlkZW50aWZ5aW5nIHRoZWlyIGJvdW5kYXJpZXMuIEl0IGlzIHBhcnRpY3VsYXJseSBlZmZlY3RpdmUgZm9yIGltYWdlcyB3aXRoIGNvbXBsZXggY2VsbCBzaGFwZXMgYW5kIHZhcnlpbmcgc2l6ZXMsIGJ1dCB0aGlzIHJlcXVpcmVkIGdvb2QgY2VsbCBib3VuZGFyeSBzdGFpbmluZyAtIHRoaXMgaXMgbm90IGF2YWlsYWJsZSBmb3Igb3VyIHRlc3QgZGF0YXNldC4gT2Z0ZW4gY2VsbCBib3VuZGFyeSBzdGFpbmluZyBjYW4gYmUgbm9uLXVuaWZvcm0gYWNyb3NzIGRpZmZlcmVudCB0aXNzdWVzLCBhZGRpbmcgZnVydGhlciBkaWZmaWN1bHRpZXMuIENlbGxwb3NlIHZlcnNpb24gMyBpbmNvcnBvcmF0ZXMgdXNlci1ndWlkZWQgbW9kZWwgdHJhaW5pbmcsIHdoaWNoIGNhbiBiZSB2ZXJ5IHVzZWZ1bCBmb3IgZGlmZmljdWx0IHRvIHNlZ21lbnQgY2VsbCB0eXBlcyAtIGJ1dCB0aGlzIHJlcXVpcmVzIHRpbWUgaW52ZXN0bWVudCB0byBhbm5vdGF0ZSB0cmFpbmluZyBleGFtcGxlcy4KCioqVHJhbnNjcmlwdC1EZW5zaXR5IEJhc2VkIFNlZ21lbnRhdGlvbioqIGFsZ29yaXRobXMsIGxpa2UgQmF5c29yIHNlZ21lbnRzIGNlbGxzIGJhc2VkIG9uIHRoZSBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiB0cmFuc2NyaXB0cy4gSXQgdXNlcyBCYXllc2lhbiBpbmZlcmVuY2UgdG8gYXNzaWduIHRyYW5zY3JpcHRzIHRvIGNlbGxzLCBjb25zaWRlcmluZyBib3RoIHRoZSBkZW5zaXR5IGFuZCBkaXN0cmlidXRpb24gb2YgUk5BIG1vbGVjdWxlcy4gVGhpcyBjYW4gYmUgdmVyeSB1c2VmdWwgZm9yIGltcHJvdmluZyBjZWxsIHNlZ21lbnRhdGlvbiB3aGVyZSBjZWxsIGJvdW5kYXJ5IHN0YWluIGlzIG5vdCBhdmFpbGFibGUgb3Igbm90IHdvcmtpbmcgd2VsbC4KCgpJbiB0aGlzIGNhc2UsIHdlIHdpbGwgdHJ5IHJlLXNlZ21lbnRpbmcgb3VyIGRhdGEgd2l0aCBCYXlzb3IuIEhlcmUncyB0aGUgcnVuIHdlIHByZXBhcmVkIGVhcmxpZXIgLSBzZWUgc3VwcGxlbWVudGFyeSBtYXRlcmlhbCBvbiBob3cgdG8gcHJvY2VzcyB0aGUgZGF0YSB5b3Vyc2VsZi4gCgpgYGB7cn0KYmF5c29yIDwtICIvcHJvamVjdC9zaGFyZWQvc3BhdGlhbF9kYXRhX2NhbXAvZGF0YXNldHMvUFJFQ09NUFVURUQvYmF5c29yIgpgYGAKClRoZSBrZXkgb3V0cHV0IG9mIGJheXNvciBpcyB0aGUgZmlsZSB3aXRoIHRyYW5zY3JpcHRzLCB3aGljaCBoYXZlIGJlZW4gcmUtYXNzaWduZWQgdG8gYSBuZXcgY2VsbCBpZGVudGlmaWVyLgpgYGB7cn0Kc2VnIDwtIHJlYWRfY3N2KGZpbGUucGF0aChiYXlzb3IsICJzZWdtZW50YXRpb24uY3N2IikpCgpoZWFkKHNlZykKCmBgYHtyfQojCmBgYAoKYGBgClRoZXJlIHdpbGwgYmUgc29tZSB0cmFuc2NyaXB0cyB0aGF0IGNhbm5vdCBiZSBhc3NpZ25lZCB0byBhIGNlbGwgLSBhYm91dCAxMCUgaW4gdGhpcyBjYXNlLiBUaGlzIGluZm9ybWF0aW9uIGlzIHN0b3JlZCB1bmRlciAiaXNfbm9pc2UiIGZsYWcuIApUaGlzIGlzIGZhaXJseSBub3JtYWwgbGV2ZWxzIG9mIG5vaXNlLgpgYGB7cn0KdGFibGUoc2VnJGlzX25vaXNlKQpgYGAKCgoKCgpgYGB7cn0KcXBsb3Qoc2VnJGFzc2lnbm1lbnRfY29uZmlkZW5jZSkKdGFibGUoc2VnJGFzc2lnbm1lbnRfY29uZmlkZW5jZSA+IC45KQpgYGAKQW5kIHRyYW5zY3JpcHQgY29uZmlkZW5jZSAtIHRoZSBjb25maWRlbmNlIHRoYXQgdGhlIG1vbGVjdWxlIGl0c2VsZiBpcyByZWFsIGFuZCBub3Qgbm9pc2UuCgpgYGB7cn0KcXBsb3Qoc2VnJGNvbmZpZGVuY2UpCnRhYmxlKHNlZyRjb25maWRlbmNlID4gLjkpCmBgYAoKV2UgY2FuIGZpbHRlciBvdXQgbG93IGNvbmZpZGVuY2UgYW5kIGxvdyBhc3NpZ25tZW50IGNvbmZpZGVuY2UgdHJhbnNjcmlwdHMgaGVyZSBmcm9tIGZ1cnRoZXIgYW5hbHlzaXMuIEhvdyBzdHJpbmdlbnQgeW91IHdhbnQgdG8gYmUgZGVwZW5kcyBvbiB3aGV0aGVyIHlvdSB3YW50IHRvIGtlZXAgYXMgbXVjaCBkYXRhIGFzIHBvc3NpYmxlIGFuZCBhY2NlcHQgc29tZSBpbmFjY3VyYWNpZXMsIG9yIGVuZCB1cCB3aXRoIHRoZSBjbGVhbmVzdCBwb3NzaWJsZSBkYXRhc2V0LgoKSGVyZSwgd2Ugd2lsbCBmaWx0ZXIgb3V0IHRyYW5zY3JpcHRzIHRoYXQgaGF2ZSBub3QgYmVlbiBhc3NpZ25lZCB0byBjZWxscywgYW5kIGJlbG93IDAuOSBjb25maWRlbmNlIGFuZCBhc3NpZ25tZW50IGNvbmZpZGVuY2UuCgpUaGVuLCB3ZSB0YWJ1bGF0ZSBhIGNlbGwgYnkgZ2VuZSBtYXRyaXggZnJvbSB0aGVzZSBkYXRhLgoKYGBge3J9CmZpbHRlcmVkIDwtIHNlZ1tzZWckY29uZmlkZW5jZSA+IC45ICYgc2VnJGFzc2lnbm1lbnRfY29uZmlkZW5jZSA+IC45ICYgIXNlZyRpc19ub2lzZSwgXQptYXQgPC0gdGFibGUoZmlsdGVyZWQkZ2VuZSwgZmlsdGVyZWQkY2VsbCkKbWF0IDwtIG1hdHJpeChtYXQsIG5jb2wgPSBuY29sKG1hdCksIGRpbW5hbWVzID0gZGltbmFtZXMobWF0KSkKYGBgCgoKQmF5c29yIGZ1cnRoZXIgcHJvdmlkZXMgZGlhZ25vc3RpYyBpbmZvIGFib3V0IGNlbGxzIGluICoic2VnbWVudGF0aW9uX2NlbGxfc3RhdHMuY3N2IiogZmlsZSwgd2hpY2ggd2Ugd2lsbCBhbHNvIHJlYWQgaW4gaGVyZS4gVGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzIGNhbiBiZSB1c2VkIHRvIGZpbHRlciBsb3ctcXVhbGl0eSBjZWxsczoKCiphcmVhOiogYXJlYSBvZiB0aGUgY29udmV4IGh1bGwgYXJvdW5kIHRoZSBjZWxsIG1vbGVjdWxlcwoqYXZnX2NvbmZpZGVuY2U6KiBhdmVyYWdlIGNvbmZpZGVuY2Ugb2YgdGhlIGNlbGwgbW9sZWN1bGVzCipkZW5zaXR5OiogdGhlIG51bWJlciBvZiBtb2xlY3VsZXMgaW4gYSBjZWxsIGRpdmlkZWQgYnkgdGhlIGNlbGwgYXJlYQoqZWxvbmdhdGlvbjoqIHJhdGlvIG9mIHRoZSB0d28gZWlnZW52YWx1ZXMgb2YgdGhlIGNlbGwgY292YXJpYW5jZSBtYXRyaXgKKm5fdHJhbnNjcmlwdHM6KiBudW1iZXIgb2YgbW9sZWN1bGVzIHBlciBjZWxsCiphdmdfYXNzaWdubWVudF9jb25maWRlbmNlOiogYXZlcmFnZSBhc3NpZ25tZW50IGNvbmZpZGVuY2UgcGVyIGNlbGwuIENlbGxzIHdpdGggbG93IGF2Z19hc3NpZ25tZW50X2NvbmZpZGVuY2UgaGF2ZSBhIG11Y2ggaGlnaGVyIGNoYW5jZSBvZiBiZWluZyBhbiBhcnRpZmFjdC4KKm1heF9jbHVzdGVyX2ZyYWMgKG9ubHkgaWYgbi1jbHVzdGVycyA+IDEpKjogZnJhY3Rpb24gb2YgdGhlIG1vbGVjdWxlcyBjb21pbmcgZnJvbSB0aGUgbW9zdCBwb3B1bGFyIGNsdXN0ZXIuIENlbGxzIHdpdGggbG93IG1heF9jbHVzdGVyX2ZyYWMgYXJlIG9mdGVuIGRvdWJsZXRzLgoqbGlmZXNwYW4qOiBudW1iZXIgb2YgaXRlcmF0aW9ucyB0aGUgZ2l2ZW4gY29tcG9uZW50IGV4aXN0cy4gVGhlIG1heGltYWwgbGlmZXNwYW4gaXMgY2xpcHBlZCBwcm9wb3J0aW9uYWxseSB0byB0aGUgdG90YWwgbnVtYmVyIG9mIGl0ZXJhdGlvbnMuIENvbXBvbmVudHMgd2l0aCBhIHNob3J0IGxpZmVzcGFuIGxpa2VseSBjb3JyZXNwb25kIHRvIG5vaXNlLgoKCmBgYHtyfQpzdGF0cyA8LSByZWFkX2NzdihmaWxlLnBhdGgoYmF5c29yLCAic2VnbWVudGF0aW9uX2NlbGxfc3RhdHMuY3N2IikpCnN0YXRzIDwtIGFzLmRhdGEuZnJhbWUoc3RhdHMpCnJvd25hbWVzKHN0YXRzKSA8LSBzdGF0cyRjZWxsCmhlYWQoc3RhdHMpCmBgYApOb3csIHdlIGNhbiBhc3NlbWJsZSBhIHNldXJhdCBvYmplY3QgYXMgYmVmb3JlIC0gd2UgZmlyc3QgY29uc3RydWN0IGEgYmFzaWMgb2JqZWN0IHdpdGggY2VsbCBieSBnZW5lIG1hdHJpeCBhbmQgY2VsbCBtZXRhIGRhdGEuCmBgYHtyfQpzZXVyYXRfcmVzZWcgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IG1hdCwgYXNzYXkgPSAiWEVOSVVNIiwgbWV0YS5kYXRhID0gYXMuZGF0YS5mcmFtZShzdGF0cykpCmBgYApEaWZmZXJlbnQgc2VnbWVudGF0aW9uIGFsZ29yaXRobXMgb3V0cHV0IGNlbGwgYm91bmRhcmllcyBpbiB2YXJpb3VzIGNlbGwgZm9ybWF0cy4gCgpHZW9KU09OIGlzIGEgZm9ybWF0IGZvciBlbmNvZGluZyB2YXJpb3VzIGdlb2dyYXBoaWMgZGF0YSBzdHJ1Y3R1cmVzIHVzaW5nIEphdmFTY3JpcHQgT2JqZWN0IE5vdGF0aW9uIChKU09OKS4gSXQgaXMgd2lkZWx5IHVzZWQgZm9yIHJlcHJlc2VudGluZyBzcGF0aWFsIGZlYXR1cmVzIGFuZCB0aGVpciBhdHRyaWJ1dGVzIGFuZCBpcyB1c2VkIGJ5IHNvbWUgYWxnb3JpdGhtcyB0byBzdG9yZSBhbmQgb3V0cHV0IGNlbGwgc2VnbWVudGF0aW9uIGJvdW5kYXJpZXMuIAoKSW4gUiwgd2UgY2FuIHJlYWQgaW4gcG9seWdvbiBkYXRhIGZyb20gYSBHZW9KU09OIGZpbGUgdXNpbmcgdGhlIEZST01fR2VvSnNvbiBmdW5jdGlvbi4gCgoqTk9URTogYmF5c29yIGlzIGEgM0QgY2VsbCBzZWdtZW50YXRpb24gYWxnb3JpdGhtLiBUaGlzIG1lYW5zIGl0IGNvbnNpZGVyZWQgei1zdGFjayBpbmZvcm1hdGlvbi4gU29tZSBhbGdvcml0aG1zIG9ubHkgcGVyZm9ybSBjZWxsIHNlZ21lbnRhdGlvbiBvbiBhIHJlcHJlc2VudGF0aXZlIGxheWVyIC0gZS5nLiBNZXJzY29wZSBDZWxscG9zZSB0YWtlcyB0aGUgbWlkZGxlIHotc3RhY2suIFRoaXMgcmVzZWdtZW50ZWQgZGF0YSByZXByZXNlbnRzIDNEIHNlZ21lbnRhdGlvbnMgb2YgY2VsbHMuIFNpbmNlIHRoZXNlIGFyZSAzRCBzZWdtZW50YXRpb25zLCB0aGV5IG1heSBsb29rIHVudXN1YWwgd2hlbiB2aXN1YWxpemVkIGFzIDJEIHByb2plY3Rpb25zLioKCgpgYGB7cn0KcG9seWdvbnMgPC0gRlJPTV9HZW9Kc29uKGZpbGUucGF0aChiYXlzb3IsICJzZWdtZW50YXRpb25fcG9seWdvbnMuanNvbiIpKQoKYGBgCgpJbiB0aGUgYmVsb3cgY29kZSwgd2UgZXh0cmFjdCB0aGUgcG9seWdvbiBjb29yZGluYXRlcyBmcm9tIHRoZSBkYXRhIGFuZCByZWZvcm1hdCB0aGVtIGludG8gYSBkYXRhIGZyYW1lIHRoYXQgU2V1cmF0IHJlcXVpcmVzIHRvIGNvbnN0cnVjdCBhIFNlZ21lbnRhdGlvbiBvYmplY3QuCgpgYGB7cn0KcG9seWdvbnMgPC0gbGFwcGx5KDE6bGVuZ3RoKHBvbHlnb25zJGdlb21ldHJpZXMpLCBGVU49ZnVuY3Rpb24oeCl7CiAgZGYgPC0gYXMuZGF0YS5mcmFtZShwb2x5Z29ucyRnZW9tZXRyaWVzW1t4XV0kY29vcmRpbmF0ZXMpCiAgZGYkY2VsbF9pZCA8LSBwYXN0ZTAoIkNSZWY5Njk0YzU3LSIsIHgpCiAgZGYKICB9KQoKcG9seWdvbnMgPC0gZG8uY2FsbChyYmluZCwgcG9seWdvbnMpCmNvbG5hbWVzKHBvbHlnb25zKSA8LSBjKCJ4IiwgInkiLCAiY2VsbF9pZCIpCnBvbHlnb25zIDwtIHBvbHlnb25zW3BvbHlnb25zJGNlbGxfaWQgJWluJSBDZWxscyhzZXVyYXRfcmVzZWcpLCBdCnBvbHlnb25zIDwtIENyZWF0ZVNlZ21lbnRhdGlvbihwb2x5Z29ucykKYGBgCgpUaGVuLCBhcyBiZWZvcmUsIHdlIGFkZCBib3RoIHRoZSBjZWxsIGNlbnRyb2lkIGFuZCBjZWxsIGJvdW5kYXJpZXMgYXMgc2VnbWVudGF0aW9ucyB0byB0aGUgc2V1cmF0IG9iamVjdC4gV2Ugc2tpcCBhZGRpbmcgaW5kaXZpZHVhbCBtb2xlY3VsZSBjb29yZGluYXRlcyBmb3Igbm93LgpgYGB7cn0KY2VudHMgPC0gQ3JlYXRlQ2VudHJvaWRzKHN0YXRzW0NlbGxzKHNldXJhdF9yZXNlZyksIGMoIngiLCAieSIpXSkKY2VudHNAY2VsbHMgPC0gQ2VsbHMoc2V1cmF0X3Jlc2VnKQpjb29yZHMgPC0gQ3JlYXRlRk9WKGNvb3JkcyA9bGlzdChjZW50cm9pZHMgPSBjZW50cywgc2VnbWVudGF0aW9uPXBvbHlnb25zKSAsCiAgICAgICAgICAgICAgICAgICAgdHlwZSA9IGMoImNlbnRyb2lkcyIsICJzZWdtZW50YXRpb24iKSwgCiAgICAgICAgICAgICAgICAgICAgbW9sZWN1bGVzID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJYRU5JVU0iKQoKc2V1cmF0X3Jlc2VnW1siQ09MT04iXV0gPC0gY29vcmRzCmBgYAoKRnJvbSBoZXJlLCB3ZSBjYW4gdXNlIHRoZSBzZXVyYXQgb2JqZWN0IHRvIHZpc3VhbGlzZSB2YXJpb3VzIGNlbGwgbWV0YSBkYXRhIC0gZm9yIGV4YW1wbGUsIGF2ZXJhZ2UgdHJhbnNjcmlwdCBhc3NpZ25tZW50IGNvbmZpZGVuY2UgcGVyIGNlbGwuIApgYGB7cn0KSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfcmVzZWcsICJhdmdfYXNzaWdubWVudF9jb25maWRlbmNlIiApICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKTGV0cyBmaWx0ZXIgb3V0IGxvdyBjb3VudCBjZWxscyBhbmQgcmUtY2x1c3RlciB0aGUgZGF0YSBhcyBiZWZvcmUKCmBgYHtyfQpzZXVyYXRfcmVzZWckRklMVCA8LSBzZXVyYXRfcmVzZWckbkNvdW50X1hFTklVTSA+PSAxNQpzZXVyYXRfcmVzZWcgPC0gc3Vic2V0KHNldXJhdF9yZXNlZywgRklMVCkKc2V1cmF0X3Jlc2VnIDwtIFNDVHJhbnNmb3JtKHNldXJhdF9yZXNlZywgYXNzYXkgPSAiWEVOSVVNIiwgY2xpcC5yYW5nZSA9IGMoLTEwLCAxMCkpCnNldXJhdF9yZXNlZyA8LSBSdW5QQ0Eoc2V1cmF0X3Jlc2VnKQpzZXVyYXRfcmVzZWcgPC0gUnVuVU1BUChzZXVyYXRfcmVzZWcsIGRpbXMgPSAxOjIwKQpzZXVyYXRfcmVzZWcgPC0gRmluZE5laWdoYm9ycyhzZXVyYXRfcmVzZWcsIHJlZHVjdGlvbiA9ICJwY2EiLCBkaW1zID0gMToyMCkKc2V1cmF0X3Jlc2VnIDwtIEZpbmRDbHVzdGVycyhzZXVyYXRfcmVzZWcsIHJlc29sdXRpb24gPSAwLjMpCmBgYApWaXN1YWxpc2luZyBjbHVzdGVycywgd2UgY2FuIHNlZSB0aGF0IHdlIGFscmVhZHkgb2J0YWluIGEgYmV0dGVyIHNlcGFyYXRpb24gaW4gdGhlIFVNQVAgZW1iZWRkaW5nIHRoYW4gYmVmb3JlLiBUaG91Z2ggb2YgY291cnNlLCBkaXN0YW5jZXMgaW4gdGhlIFVNQVAgc3BhY2UgY2FuIGJlIHZlcnkgbWlzbGVhZGluZyBhbmQgY2FyZWZ1bCBpbnRlcnByZXRhdGlvbiBpcyByZXF1aXJlZC4gCmBgYHtyfQpEaW1QbG90KHNldXJhdF9yZXNlZywgbGFiZWw9VCwgcmVwZWwgPSBUKQoKYGBgCk5leHQgd2UgdmlzdWFsaXNlIHRoZSBjbHVzdGVycyBpbiB0aXNzdWUgc3BhY2UuIApgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9yZXNlZykKYGBgCkFzIGJlZm9yZSwgbGV0cyBjcm9zcy1jbGFzc2lmeSBvdXIgY2VsbHMgdXNpbmcgdGhlIHJlZmVyZW5jZSBzaW5nbGUgY2VsbCBkYXRhc2V0CmBgYHtyfQphbmNob3JzIDwtIEZpbmRUcmFuc2ZlckFuY2hvcnMocmVmZXJlbmNlID0gcmVmLCBxdWVyeSA9IHNldXJhdF9yZXNlZywgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiU0NUIikKCnNldXJhdF9yZXNlZyA8LSBUcmFuc2ZlckRhdGEoYW5jaG9yc2V0ID0gYW5jaG9ycywgcmVmZGF0YSA9IHJlZiRDZWxsVHlwZSwgcHJlZGljdGlvbi5hc3NheSA9IFRSVUUsCiAgICB3ZWlnaHQucmVkdWN0aW9uID0gc2V1cmF0X3Jlc2VnW1sicGNhIl1dLCBxdWVyeSA9IHNldXJhdF9yZXNlZywgZGltcz0xOjMwKQoKYGBgClZpc3VhbGlzaW5nIHRoZSBwcmVkaWN0aW9ucywgd2UndmUgc2VwYXJhdGVkIFQtQ2VsbHMgZnJvbSBCLUNlbGxzIG11Y2ggYmV0dGVyLiAgVGhlIHN0cm9tYWwgY2x1c3RlcnMgc3RpbGwgcHJlZGljdCBwb29ybHksIGJ1dCB0aGF0IGlzIGR1ZSB0byBwb29yIHByb2JlIGNvdmVyYWdlLiAKYGBge3J9CkRpbVBsb3Qoc2V1cmF0X3Jlc2VnLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWQiKQpgYGAKV2UgY2FuIGNoZWNrIHRoZSBkaXN0cmlidXRpb24gaW4gdGlzc3VlIHNwYWNlOgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9yZXNlZywgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkIikKYGBgCgpgYGB7cn0KRmVhdHVyZVBsb3Qoc2V1cmF0X3Jlc2VnLCJwcmVkaWN0ZWQuaWQuc2NvcmUiKQpgYGAKICNza2lwcGVkIHNlZ21lbnRhaXRvbiwgbW92ZWQgc3RyZWlnaHQgdG8gbmVhcmVzdCBuZWlnaGJvdXIuCiAKKipTcGF0aWFsIE5laWdoYm91cmhvb2QgQW5hbHlpcyoqCgpTcGF0aWFsIG5laWdoYm91cmhvb2QgYW5hbHlzaXMgaWRlbnRpZmllcyBjZWxscyB0aGF0IGFyZSBzcGF0aWFsbHkgY2xvc2UgdG8gZWFjaCBvdGhlciB3aXRoaW4gYSB0aXNzdWUgc2VjdGlvbi4gVGhpcyB0ZWNobmlxdWUgaGVscHMgdG8gdW5kZXJzdGFuZCB0aGUgc3BhdGlhbCBvcmdhbml6YXRpb24gYW5kIHBvdGVudGlhbCBpbnRlcmFjdGlvbnMgYmV0d2VlbiBjZWxscy4gVGhlIHNhbWUgcHJpbmNpcGxlcyB1c2VkIGluIFZpc2l1bSBkYXRhIGNhbiBiZSBhcHBsaWVkIHRvICppbiBzaXR1KiBkYXRhLgoKKkdldFRpc3N1ZUNvb3JkaW5hdGVzKjogUmV0cmlldmVzIHRoZSBzcGF0aWFsIGNvb3JkaW5hdGVzIG9mIHRoZSBjZW50cm9pZHMgZnJvbSB0aGUgU2V1cmF0IG9iamVjdC4Kd2hpY2ggPSAiY2VudHJvaWRzIjogU3BlY2lmaWVzIHRoYXQgdGhlIGNlbnRyb2lkcycgY29vcmRpbmF0ZXMgc2hvdWxkIGJlIHJldHJpZXZlZC4Kcm93bmFtZXMoY29vcmRzKSA8LSBjb29yZHMkY2VsbDogU2V0cyB0aGUgcm93IG5hbWVzIG9mIHRoZSBjb29yZHMgZGF0YSBmcmFtZSB0byB0aGUgY2VsbCBJRHMuCgoqKkZpbmROZWlnaGJvcnMqKjogSWRlbnRpZmllcyB0aGUgbmVhcmVzdCBuZWlnaGJvdXJzIGZvciBlYWNoIGNlbGwgYmFzZWQgb24gdGhlaXIgc3BhdGlhbCBjb29yZGluYXRlcy4KYXMubWF0cml4KGNvb3Jkc1ssIGMoIngiLCAieSIpXSk6IENvbnZlcnRzIHRoZSB4IGFuZCB5IGNvb3JkaW5hdGVzIHRvIGEgbWF0cml4IGZvcm1hdC4Kay5wYXJhbSA9IDIwOiBTcGVjaWZpZXMgdGhlIG51bWJlciBvZiBuZWFyZXN0IG5laWdoYm91cnMgdG8gaWRlbnRpZnkgZm9yIGVhY2ggY2VsbC4KcmV0dXJuLm5laWdoYm9yID0gVFJVRTogRW5zdXJlcyB0aGF0IHRoZSBmdW5jdGlvbiByZXR1cm5zIHRoZSBuZWlnaGJvdXIgaW5kaWNlcyBhbmQgZGlzdGFuY2VzLgoKCipUSVA6IFRoaXMgYXBwcm9hY2ggaWRlbnRpZmllcyBzcGF0aWFsIG5laWdoYm91cnMuIElmIHlvdSBhbmFseXNpcyByZXF1aXJlcyBwcmVjaXNlIGlkZW50aWZpY2F0aW9uIG9mIGRpcmVjdGx5IGFkamFjZW50IG9yIGludGVyYWN0aW5nIGNlbGwgbmVpZ2hib3VycywgdGhlbiBhIGRlbGF1bmF5IG5ldHdvcmsgYmFzZWQgYXBwcm9hY2ggd291bGQgYmUgbW9yZSBhcHByb3ByaWF0ZS4gUiBwYWNrYWdlIEdpb3R0byBpbXBsZW1lbnRzIHNvbWUgbmljZSBmdW5jdGlvbmFsaXRpZXMgYmFzZWQgb24gdGhpcyoKCgoKYGBge3J9CmNvb3JkcyA8LSBHZXRUaXNzdWVDb29yZGluYXRlcyhzZXVyYXRfQ1JDMSwgd2hpY2ggPSAiY2VudHJvaWRzIikKcm93bmFtZXMoY29vcmRzKSA8LSBjb29yZHMkY2VsbApuZWlnaGJvdXJzIDwtIEZpbmROZWlnaGJvcnMoYXMubWF0cml4KGNvb3Jkc1ssIGMoIngiLCAieSIpXSksIGsucGFyYW0gPSAyMCwgcmV0dXJuLm5laWdoYm9yPVRSVUUpCgpgYGAKQXMgaW4gb3VyIFZpc2l1bSBleGFtcGxlLCB3ZSBjYW4gdXNlIGl0IGF1dG9tYXRpY2FsbHkgc2VsZWN0IGNlbGxzIHRoYXQgYXJlIGFkamFjZW50IG9yIHBoeXNpY2FsbHkgY2xvc2UgdG8gc29tZSBmZWF0dXJlIG9mIGludGVyZXN0LiBJbiB0aGlzIGNhc2UsIHdlIHdhbnQgdG8gYXV0b21hdGljYWxseSBzZWxlY3QgYW55IGNlbGxzIHRoYXQgYXJlIG5lYXJieSB0aGUgZ3JvdXAgb2YgY2VsbHMgd2hpY2ggaGF2ZSBiZWVuIGRlc2lnbmF0ZWQgYXMgY2x1c3RlciA4LCB3aGljaCBhcmUgbWlkLWNyeXB0IGVwaXRoZWxpYWwgY2VsbHMuIAoKVGhpcyBjYW4gYmUgdXNlZnVsIHRvIGV4cGxvcmUgaG93IGNlbGxzIGluIGFkamFjZW50IGNlbGxzIG1pZ2h0IGluZmx1ZW5jZSBvciBpbnRlcmFjdCB3aXRoIGVhY2ggb3RoZXIuCgoKKldoaWNoQ2VsbHMqIGlkZW50aWZpZXMgdGhlIGNlbGxzIHRoYXQgYmVsb25nIHRvIGEgc3BlY2lmaWMgY2x1c3RlciBvciBtZWV0IGEgcGFydGljdWxhciBleHByZXNzaW9uIGNyaXRlcmlvbgoKKlRvcE5laWdoYm9ycyogZmluZHMgdGhlIHRvcCBuIG5lYXJlc3QgbmVpZ2hib3VycyBmb3IgYSBnaXZlbiBzZXQgb2YgY2VsbHMgYmFzZWQgb24gdGhlIGstTk4gZ3JhcGguIEluY3JlYXNpbmcgdGhlIG4gaGVyZSB3aWxsIHJldHVybiBtb3JlIGFuZCBtb3JlIGRpc3RhbCBzcG90cyAtIHR3ZWFrIHRvIHlvdXIgcmVxdWlyZW1lbnRzIQoKKipIb3cgd291bGQgeW91IGFuYWx5c2UgdGhlc2UgZ3JvdXBzIGZ1cnRoZXI/IFdoYXQgYXJlIHRoZSBhZGphY2VudCBjZWxscz8gQXJlIHRoZXkgaGV0ZXJvZ2Vub3VzPyBXaGF0IGRvIHRoZXkgZXhwcmVzcz8qKgoKCmBgYHtyfQpjZWxscyA8LSBXaGljaENlbGxzKHNldXJhdF9DUkMxLCBleHByZXNzaW9uPSBTQ1Rfc25uX3Jlcy4wLjcgPT0gOCkKYWRqYWNlbnQgPC0gVG9wTmVpZ2hib3JzKG5laWdoYm91cnMsIGNlbGxzLCBuID0gMTApCgpJZGVudHMoc2V1cmF0X0NSQzEpIDwtICJPdGhlciBDZWxscyIKc2V1cmF0X0NSQzEgPC0gU2V0SWRlbnQoc2V1cmF0X0NSQzEsIGNlbGxzID0gYWRqYWNlbnQsICJBZGphY2VudCBDZWxscyIpCnNldXJhdF9DUkMxIDwtIFNldElkZW50KHNldXJhdF9DUkMxLCBjZWxscyA9IGNlbGxzLCAiQ2VsbHMgb2YgSW50ZXJlc3QiKQoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMxKQoKc2V1cmF0X0NSQzFbWyJncm91cDEiXV0gPC0gSWRlbnRzKHNldXJhdF9DUkMxKQpgYGAKKipGaW5kaW5nIFNwYXRpYWxseSBDb3JyZWxhdGVkIEdlbmVzKioKCldlIGNhbiB1c2UgdGhlIHNwYXRpYWwgbmVpZ2hib3VyaG9vZCBncmFwaCB0byBpZGVudGlmeSBzcGF0aWFsbHkgY29ycmVsYXRlZCBmZWF0dXJlcy4gCgpXZSBzdGFydCBieSBjYWxjdWxhdGluZywgZm9yIGVhY2ggY2VsbCwgdGhlIHBzZXVkb2J1bGsgZXhwcmVzc2lvbiBvZiBhbGwgY2VsbHMgaW4gaXQncyBsb2NhbCBuZWlnaGJvdXJob29kLgpGaXJzdCBvZiBhbGwsIGxldHMgZXhwYW5kIHRoZSBrLnBhcmFtIHRvIGluY2x1ZGUgbW9yZSBuZWlnaGJvdXJzIC0gdGhpcyB3aWxsIGNvbnRyb2wgd2hldGhlciB3ZSdyZSBpbmNsdWRpbmcgbW9yZSBkaXN0YWwgZ2VuZSBleHByZXNzaW9uLiAKCkZpbmROZWlnaGJvcnM6IElkZW50aWZpZXMgdGhlIG5lYXJlc3QgbmVpZ2hib3VycyBmb3IgZWFjaCBjZWxsIGJhc2VkIG9uIHRoZWlyIHNwYXRpYWwgY29vcmRpbmF0ZXMuCmFzLm1hdHJpeChjb29yZHNbLCBjKCJ4IiwgInkiKV0pOiBDb252ZXJ0cyB0aGUgeCBhbmQgeSBjb29yZGluYXRlcyB0byBhIG1hdHJpeCBmb3JtYXQuCmsucGFyYW0gPSA1MDogU3BlY2lmaWVzIHRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvdXJzIHRvIGlkZW50aWZ5IGZvciBlYWNoIGNlbGwuCgpMYXllckRhdGE6IEV4dHJhY3RzIHRoZSBnZW5lIGV4cHJlc3Npb24gZGF0YSBmcm9tIHRoZSBzcGVjaWZpZWQgbGF5ZXIgYW5kIGFzc2F5IGluIHRoZSBTZXVyYXQgb2JqZWN0LgpsYXllciA9ICJjb3VudHMiOiBTcGVjaWZpZXMgdGhhdCB0aGUgY291bnRzIGxheWVyIHNob3VsZCBiZSBleHRyYWN0ZWQuCmFzc2F5ID0gIlhFTklVTSI6IFNwZWNpZmllcyB0aGUgYXNzYXkgZnJvbSB3aGljaCB0byBleHRyYWN0IHRoZSBkYXRhLgoKYXMubWF0cml4KG5laWdoYm91cnMkbm4gJSolIHQobXQpKTogTXVsdGlwbGllcyB0aGUgbmVpZ2hib3VyIG1hdHJpeCB3aXRoIHRoZSB0cmFuc3Bvc2Ugb2YgdGhlIGdlbmUgZXhwcmVzc2lvbiBtYXRyaXggdG8gYWdncmVnYXRlIHRoZSBleHByZXNzaW9uIGxldmVscyBvZiBuZWlnaGJvdXJpbmcgY2VsbHMuCgpgYGB7cn0KbmVpZ2hib3VycyA8LSBGaW5kTmVpZ2hib3JzKGFzLm1hdHJpeChjb29yZHNbLCBjKCJ4IiwgInkiKV0pLCBrLnBhcmFtID0gNTApCm10IDwtIExheWVyRGF0YShzZXVyYXRfQ1JDMSwgbGF5ZXIgPSAiY291bnRzIiwgYXNzYXkgPSAiWEVOSVVNIikKc3VtX210eCA8LSBhcy5tYXRyaXgobmVpZ2hib3VycyRubiAlKiUgdChtdCkpCgpgYGAKCldlIGNhbiBzdG9yZSB0aGUgbmVpZ2hib3VyaG9vZC1hZ2dyZWdhdGVkIHZhbHVlcyBpbiBvdXIgU2V1cmF0IG9iamVjdCBhcyBhIHNlcGFyYXRlIGFzc2F5LCB3aGljaCB3ZSB3aWxsIGNhbGwgIk5FSUdIQk9VUkhPT0Q1MCIuIFdlIHRoZW4gbm9ybWFsaXNlIHRoZSBtYXRyaXguIApgYGB7cn0Kc2V1cmF0X0NSQzFbWyJORUlHSEJPVVJIT09ENTAiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QodChzdW1fbXR4KSkKc2V1cmF0X0NSQzEgPC0gTm9ybWFsaXplRGF0YShzZXVyYXRfQ1JDMSwgYXNzYXkgPSAiTkVJR0hCT1VSSE9PRDUwIikKCgpgYGAKCldlIGNhbiB0aGVuIGFwcGx5IHF1aWNrIGNvcnJlbGF0aW9uIGNhbGN1bGF0aW9ucyB0byBpZGVudGlmeSBzcGF0aWFsbHkgY29ycmVsYXRlZCBmZWF0dXJlcy4gCgoKYGBge3J9CmNvcnJnZW5lcyA8LSBjb3IoYXMubWF0cml4KHQoTGF5ZXJEYXRhKHNldXJhdF9DUkMxLCBhc3NheSA9ICJORUlHSEJPVVJIT09ENTAiLCBsYXllciA9ICJkYXRhIikpKSkKZGlhZyhjb3JyZ2VuZXMpIDwtIDAKaGlnaF9jb3JyX2dlbmVzIDwtIHdoaWNoKHJvd01heHMoY29ycmdlbmVzKSA+IC43KQpkaWFnKGNvcnJnZW5lcykgPC0gMQpoZWF0bWFwIDwtIHBoZWF0bWFwKGNvcnJnZW5lc1toaWdoX2NvcnJfZ2VuZXMsIGhpZ2hfY29ycl9nZW5lc10sIGJvcmRlcl9jb2xvciA9IE5BKQpgYGAKSGVyZSwgd2UgdXNlIGEgdmVyeSBxdWljayBjdXRyZWUgZnVuY3Rpb24gdG8gcGFydGl0aW9uIHRoZSBoaWVyYWNoaWNhbCBjbHVzdGVyaW5nIGludG8gNSBncm91cHMgb2YgY28tbG9jYWxpc2luZyBnZW5lcy4gCgoqVElQLSBmb3IgbW9yZSBhZHZhbmNlZCBtb2R1bGUgZGV0ZWN0aW9uLCB5b3UgY2FuIHVzZSB0aGUgc3BhdGlhbCBuZWlnaGJvdXJob29kIGFnZ3JlZ2F0ZWQgbWF0cml4IHdpdGggdG9vbHMgc3VjaCBhcyBXR0NOQSAodGhvdWdoIGl0IGhhcyBpdHMgb3duIG1ldGEtY2VsbCBmdW5jdGlvbmFsaXR5KS4qCgpgYGB7cn0KbW9kdWxlcyA8LSBjdXRyZWUoaGVhdG1hcCR0cmVlX3JvdywgNSkKbW9kdWxlcwpgYGAKTGV0cyB2aXN1YWxpemUgc29tZSBvZiB0aGUgZGV0ZWN0ZWQgc3BhdGlhbGx5IGNvLWxvY2FsaXppbmcgZ2VuZXMuIEZvciBleGFtcGxlLCAgbW9kdWxlIDEgZ2VuZXMgLSB3ZSBjYW4gc2VlIHRoYXQgQ0QyNCBhbmQgU09YOSBhcmUgc3BhdGlhbGx5IHNpbWlsYXIsIGJ1dCBub3QgbmVjZXNzYXJpbHkgYWx3YXlzIGV4cHJlc3NlZCBieSB0aGUgc2FtZSBjZWxscy4KYGBge3J9CgpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCAiQ0QyNCIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCAiQU5YQTEiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKCiNDRDI0IG1vc3R5IGNhbmNlciBjZWxscyAKIyBBTlhBMSBwcm9kdWNlZCBieSBtYWNyb3BoYWdlcywgbmV1dHJvcGhpbHMgYW5kIGVwaXRoZWxpYWwgY2VsbHMgCgpgYGAKYGBge3J9CkZlYXR1cmVQbG90KHNldXJhdF9DUkMxLCBjKCJBTlhBMSIsICJDRDI0IikpCmBgYAoKCgoKU2ltaWxhcmx5LCBtb2R1bGUgNCBnZW5lcyB3aGljaCBhcmUgZXhwcmVzc2VkIGJ5IGRpZmZlcmVudCBjZWxsIHR5cGVzLCBidXQgc2hvdyBzdHJvbmcgY28tbG9jYWxpc2F0aW9uOgpgYGB7cn0KSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIkFQT0UiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMSwgIk1TNEE3IikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYAoKV2UgY2FuIHVzZSBTZXVyYXQncyBtb2R1bGUgc2NvcmUgZnVuY3Rpb25hbGl0eSB0byBhZGQgdGhlIG1vZHVsZXMgdG8gdGhlIG1ldGEgZGF0YSBhcyBhbiBhZ2dyZWdhdGUgc2NvcmUgZm9yIGVhc2Ugb2YgdmlzdWFsaXNhdGlvbi4gCgpUaGUgKkFkZE1vZHVsZVNjb3JlKiBmdW5jdGlvbiBpbiBTZXVyYXQgaXMgdXNlZCB0byBjYWxjdWxhdGUgYW5kIGFkZCBzY29yZXMgZm9yIHByZWRlZmluZWQgZ2VuZSBtb2R1bGVzIHRvIGEgU2V1cmF0IG9iamVjdC4gVGhlc2UgbW9kdWxlIHNjb3JlcyByZXByZXNlbnQgdGhlIGF2ZXJhZ2UgZXhwcmVzc2lvbiBsZXZlbHMgb2YgYSBzZXQgb2YgZ2VuZXMgKG1vZHVsZXMpIGZvciBlYWNoIGNlbGwsIHdoaWNoIGNhbiBiZSB1c2VkIHRvIGlkZW50aWZ5IHNwZWNpZmljIGJpb2xvZ2ljYWwgcHJvY2Vzc2VzIG9yIGNlbGwgc3RhdGVzLgoKKnNldXJhdCo6IFRoZSBTZXVyYXQgb2JqZWN0IHRvIHdoaWNoIHRoZSBtb2R1bGUgc2NvcmVzIGFyZSBhZGRlZC4KKmZlYXR1cmVzID0gc3BsaXQobmFtZXMobW9kdWxlcyksIG1vZHVsZXMpKjogU3BlY2lmaWVzIHRoZSBnZW5lIG1vZHVsZXMuIFRoZSBzcGxpdCBmdW5jdGlvbiBpcyB1c2VkIHRvIGNyZWF0ZSBhIGxpc3Qgb2YgZ2VuZSBzZXRzLCB3aGVyZSBlYWNoIHNldCBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIG1vZHVsZS4KKmFzc2F5ID0gIlNDVCIqOiBTcGVjaWZpZXMgdGhlIGFzc2F5IGZyb20gd2hpY2ggdG8gY2FsY3VsYXRlIHRoZSBtb2R1bGUgc2NvcmVzLiBIZXJlLCAiU0NUIiByZWZlcnMgdG8gdGhlIFNDVHJhbnNmb3JtLW5vcm1hbGl6ZWQgZGF0YS4KKm5iaW4gPSAzKjogRGl2aWRlcyB0aGUgZ2VuZXMgaW50byAzIGJpbnMgYmFzZWQgb24gdGhlaXIgYXZlcmFnZSBleHByZXNzaW9uIGxldmVscyB0byBhY2NvdW50IGZvciBkaWZmZXJlbmNlcyBpbiBnZW5lIGV4cHJlc3Npb24gZGlzdHJpYnV0aW9ucy4gTmVlZCB0byByZWR1Y2UgdGhpcyBmcm9tIGRlZmF1bHRzIGFzIG91ciBudW1iZXIgb2YgbWVhc3VyZWQgZ2VuZXMgaXMgbXVjaCBsb3dlciB0aGFuIGluIHNpbmdsZSBjZWxsIGFuYWx5c2lzLgoqbmFtZSA9ICJNT0QiKjogU3BlY2lmaWVzIHRoZSBwcmVmaXggZm9yIHRoZSBtb2R1bGUgc2NvcmUgbmFtZXMuIFRoZSByZXN1bHRpbmcgc2NvcmVzIHdpbGwgYmUgbmFtZWQgIk1PRDEiLCAiTU9EMiIsIGV0Yy4KCgpgYGB7cn0Kc2V1cmF0IDwtIEFkZE1vZHVsZVNjb3JlKHNldXJhdCwgZmVhdHVyZXM9c3BsaXQobmFtZXMobW9kdWxlcyksIG1vZHVsZXMpLCBhc3NheSA9ICJTQ1QiLCBuYmluPTMsIG5hbWUgPSAiTU9EIiApCmBgYApWaXN1YWxpc2luZyBtb2R1bGUgc2NvcmVzIC0gd2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgaWRlbnRpZmllZCBhIGdyb3VwIG9mIGdlbmVzIGNvLWxvY2FsaXNpbmcgYXQgdGhlIGJhc2Ugb2YgdGhlIGVwaXRoZWxpYWwgY3J5cHRzIChNT0QxKSBhbmQgYW5vdGhlciBtb2R1bGUgb2YgZ2VuZXMgY28tbG9jYWxpc2luZyBpbiBseW1waG9pZCBmb2xsaWNsZXMuCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdCwgIk1PRDEiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXQsICJNT0Q0IikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYAoqKkRldGVjdGluZyBDZWxsdWxhciBOaWNoZXMqKgoKV2UgY2FuIHVzZSBhIHNpbWlsYXIgYXBwcm9hY2ggdG8gZGVmaW5lIGNlbGx1bGFyIG5pY2hlcy4gCgpMZXRzIHJlcGVhdCB0aGUgcHJvY2VzcyBvZiBkZWZpbmluZyBzcGF0aWFsIG5laWdoYm91cmhvb2QgZXhwcmVzc2lvbiwgd2l0aCB0aGUgZm9sbG93aW5nIG1vZGlmaWNhdGlvbnM6CgoxLiBFeHBhbmQgdGhlIG5lYXJlc3QgbmVpZ2hib3VycyBldmVuIGZ1cnRoZXIsIHRvIGNvbnNpZGVyIDEwMCBuZWFyYnkgY2VsbHMuCgoyLiBFeGNsdWRlIHRoZSB0cmFuc2NyaXB0b21lcyBvZiB0aGUgY2VsbHMgdGhlbXNlbHZlcyBhbmQgb25seSBjb25zaWRlciB0aGUgZXhwcmVzc2lvbiBvZiBuZWFyYnkgY2VsbHMuIAoKVGhpcyBhcHByb2FjaCB3aWxsIGVuYWJsZSB1cyB0byBncm91cCBjZWxscyBiYXNlZCBub3Qgb24gdGhlaXIgaWRlbnRpdHksIGJ1dCBvbiB0aGVpciBtaWNyb2Vudmlyb25tZW50LiAKCgpgYGB7cn0KbmVpZ2hib3VycyA8LSBGaW5kTmVpZ2hib3JzKGFzLm1hdHJpeChjb29yZHNbLCBjKCJ4IiwgInkiKV0pLCBrLnBhcmFtID0gMTAwKQpkaWFnKG5laWdoYm91cnMkbm4pIDwtIDAgIyBkb250IGNvdW50IHRyYW5zY3JpcHRvbWUgb2YgdGhlIGNlbGwgaXRzZWxmLCBqdXN0IG5laWdoYm91cnMKbXQgPC0gTGF5ZXJEYXRhKHNldXJhdCwgbGF5ZXIgPSAiY291bnRzIiwgYXNzYXkgPSAiWEVOSVVNIikKc3VtX210eCA8LSBhcy5tYXRyaXgobmVpZ2hib3VycyRubiAlKiUgdChtdCkpCmBgYAoKCkhvdyBpcyB0aGlzIHVzZWZ1bD8gV2VsbCwgbm93IHlvdSBjYW4gY2x1c3RlciBjZWxscyBub3Qgb24gdGhlaXIgZ2VuZSBleHByZXNzaW9uIHZhbHVlcywgYnV0IGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgb2Ygc3Vycm91bmRpbmcgY2VsbHMuIFRoaXMgZWZmZWN0aXZlbHkgcGFydGl0aW9ucyBjZWxscyBub3QgYmFzZWQgb24gdGhlaXIgaWRlbnRpdHksIGJ1dCBvbiB0aGVpciBtaWNyby1lbnZpcm9ubWVudCEKVXNpbmcgdGhpcyBhcHByb2FjaCwgeW91IGNhbiBpZGVudGlmeSB0aXNzdWUgbmljaGVzCgpBbHRlcm5hdGl2ZSBhcHByb2FjaGVzIC0geW91IGNvdWxkIGNvdW50IGNlbGwgdHlwZXMgcmF0aGVyIHRoYW4gZ2VuZSBleHByZXNzaW9uIHZhbHVlcywgYnV0IHRoYXQgcmVxdWlyZXMgeW91IHRvIGhhdmUgZmluYWxpc2VkIGNlbGwgYW5ub3RhdGlvbiBmb3IgeW91ciBkYXRhc2V0LCB3aGljaCBpcyBub3QgaWRlYWwuIFNvLCB3ZSBkbyB1bmJpYXNlZCB0cmFuc2NyaXB0b21pY3MgYXBwcm9hY2guIAoKKipIb3cgd291bGQgeW91IHJ1biB0aGlzIHdpdGggY2VsbCB0eXBlcz8qKgpgYGB7cn0Kc2V1cmF0W1siTkVJR0hCT1VSSE9PRDEwMCJdXSA8LSBDcmVhdGVBc3NheU9iamVjdCh0KHN1bV9tdHgpKQpEZWZhdWx0QXNzYXkoc2V1cmF0KSA8LSAiTkVJR0hCT1VSSE9PRDEwMCIKc2V1cmF0IDwtIE5vcm1hbGl6ZURhdGEoc2V1cmF0KQpzZXVyYXQgPC0gU2NhbGVEYXRhKHNldXJhdCwgZmVhdHVyZXMgPSByb3duYW1lcyhzZXVyYXQpKQpzZXVyYXQgPC0gUnVuUENBKHNldXJhdCwgZmVhdHVyZXMgPSByb3duYW1lcyhzZXVyYXQpKQpzZXVyYXQgPC0gRmluZE5laWdoYm9ycyhzZXVyYXQsIHJlZHVjdGlvbiA9ICJwY2EiLCBkaW1zID0gMToxMCkKc2V1cmF0IDwtIEZpbmRDbHVzdGVycyhzZXVyYXQsIHJlc29sdXRpb24gPSAwLjEsIGNsdXN0ZXIubmFtZSA9ICJOaWNoZXMiKQoKYGBgCgpMZXRzIHZpc3VhbGlzZSB0aGUgZGV0ZWN0ZWQgIm5pY2hlcyIuIFdlIGNhbiBzZWUgdGhhdCB3ZSBoYXZlIGFjaGlldmVkIGEgY29hcnNlIHBhcnRpb25pbmcgb2YgdGhlIGNlbGxzIGludG8gY3J5cHQgdG9wLCBtaWQtY3J5cHQgYW5kIGNyeXB0LWJhc2UgcmVnaW9ucywgYXMgd2VsbCBhcyBzZWdtZW50aW5nIG91dCBmb2xsaWNsZXMgYW5kIHN1Yi1tdWNvc2FsIHN0cm9tYS4KCioqSG93IHdvdWxkIHlvdSB0d2VhayB0aGUgYWJvdmUgYXBwcm9hY2ggdG8gZ2VuZXJhdGUgbW9yZSBvciBsZXNzIGdyYW51bGFyIG5pY2hlcz8qKgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdCwgZ3JvdXAuYnkgPSAiTmljaGVzIikKYGBgCgpXZSBjYW4gdGFidWxhdGUgb3VyIGRldGVjdGVkIG5pY2hlcyB3aXRoIHByZWRpY3RlZCBjZWxsIHR5cGUgbGFiZWxzIChvciBjbHVzdGVycykgdG8gdmlzdWFsaXNlIGVucmljaG1lbnQgb2YgZGlmZmVyZW50IGNlbGwgdHlwZXMgYWNyb3NzIHNwYXRpYWwgbmljaGVzLiAKCkZvciBleGFtcGxlLCBhcyBjb3VsZCBiZSBleHBlY3RlZCwgVC1DZWxscyBhbmQgQi1DZWxscyBlbnJpY2ggaW4gTmljaGUgMiAoZm9sbGljdWxhcikuCgpgYGB7cn0KY29tcCA8LSB0YWJsZShzZXVyYXQkTmljaGVzLCBzZXVyYXQkcHJlZGljdGVkLmlkKQpwaGVhdG1hcChjb21wLCBzY2FsZT0icm93IikKYGBgCgoKYGBge3J9CmNvbXAgPC0gdGFibGUoc2V1cmF0JE5pY2hlcywgc2V1cmF0JFNDVF9zbm5fcmVzLjAuNykKcGhlYXRtYXAoY29tcCwgc2NhbGU9InJvdyIpCmBgYAoqKkRldGVjdGluZyBBZGphY2VuY3kgRGVwZW5kZW50IEdlbmUgRXhwcmVzc2lvbiBEaWZmZXJlbmNlcyoqCgpXaGF0IGlmIHdlIHdhbnQgdG8ga25vdyBob3cgZXhwcmVzc2lvbiBwcm9maWxlcyBvZiBzcGVjaWZpYyBjZWxsIHR5cGVzIGNoYW5nZSBiYXNlZCBvbiB3aGF0IHRoZXkncmUgYWRqYWNlbnQgdG8/CldlIGNhbiB1c2UgdGhlIGV4YWN0bHkgc2FtZSBhcHByb2FjaCwgZXhjZXB0IGZvciBlYWNoIGNlbGwgbmVpZ2hib3VyaG9vZCwgd2Ugb25seSBhZ2dyZWdhdGUgdGhlIGV4cHJlc3Npb24gb2YgY2VsbCB0eXBlcyBvZiBpbnRlcmVzdC4KClNvIG5vdyBmb3IgZWFjaCBjZWxsLCBvdXIgbWF0cml4IGlzIHRoZSBzdW0gb2YgZXhwcmVzc2lvbiB2YWx1ZXMgb2YgbmVhcmJ5IHNwZWNpZmljIGNlbGwgdHlwZXMuIAoKQXMgYW4gZXhhbXBsZSwgaGVyZSB3ZSB3aWxsIGFzayBob3cgZXhwcmVzc2lvbiBvZiBlcGl0aGVsaWFsIGNlbGxzIGNoYW5nZXMgZGVwZW5kaW5nIG9uIHdoYXQgdGhleSBhcmUgYWRqYWNlbnQgdG8uICBGb3Igc2ltcGxpY2l0eSwgaGVyZSB3ZSB3aWxsIHVzZSBwcmVkaWN0ZWQgY2VsbCB0eXBlIElEcyBmcm9tIHNpbmdsZSBjZWxsIHJlZmVyZW5jZSBkYXRhLgoKKipIb3cgd291bGQgeW91IHNlbGVjdCBhIGRpZmZlcmVudCBncm91cCBvZiBjZWxscz8qKgoKYGBge3J9Cm5laWdoYm9ycyA8LSBGaW5kTmVpZ2hib3JzKGFzLm1hdHJpeChjb29yZHNbLCBjKCJ4IiwgInkiKV0pLCBrLnBhcmFtID0gMTApCm5laWdoYm9ycyRubiA8LSBuZWlnaGJvcnMkbm5bY29vcmRzJGNlbGwsIGNvb3JkcyRjZWxsXQpkaWFnKG5laWdoYm9ycyRubikgPC0gMCAjIGRvbnQgY291bnQgdHJhbnNjcmlwdG9tZSBvZiB0aGUgY2VsbCBpdHNlbGYsIGp1c3QgbmVpZ2hib3Vycz8KbXQgPC0gTGF5ZXJEYXRhKHNldXJhdCwgbGF5ZXIgPSAiY291bnRzIiwgYXNzYXkgPSAiWEVOSVVNIilbLCBjb29yZHMkY2VsbF0KbXRbLCBzZXVyYXQkcHJlZGljdGVkLmlkICE9ICJFcGl0aGVsaXVtIl0gPC0gMCAjIFplcm8gY291bnRzIGZvciBjZWxscyB0aGF0IGFyZSBub3Qgb2YgaW50ZXJlc3QKc3VtX210eCA8LSBhcy5tYXRyaXgobmVpZ2hib3JzJG5uICUqJSB0KG10KSkKCgoKYGBgCkFzIGJlZm9yZSwgd2Ugc3RvcmUgdGhlIGFnZ3JlZ2F0ZWQgbWF0cml4IGFzIGEgbmV3IGFzc2F5LCBleGNlcHQgaGVyZSB3ZSBmdXJ0aGVyIGZpbHRlciBvdXQgYW55IGNlbGxzIHRoYXQgZG8gbm90IGhhdmUgYW55IGVwaXRoZWxpYWwgY2VsbHMgbmVhcmJ5LgpgYGB7cn0Kc2V1cmF0W1siRVBJIl1dIDwtIENyZWF0ZUFzc2F5T2JqZWN0KHQoc3VtX210eFtyb3dTdW1zKHN1bV9tdHgpID4gMCwgXSkpCkRlZmF1bHRBc3NheShzZXVyYXQpIDwtICJFUEkiCnNldXJhdCA8LSBOb3JtYWxpemVEYXRhKHNldXJhdCkKYGBgCgpTbywgaG93IGRvZXMgZ2VuZSBleHByZXNzaW9uIG9mIGVwaXRoZWxpYWwgY2VsbHMgY2hhbmdlIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZXkgYXJlIGluIHByb3hpbWl0eSB0byBtYWNyb3BoYWdlcyAoY2x1c3RlciA5KSwgb3IgbXlvZmlicm9ibGFzdHMgKGNsdXN0ZXIgMCkuCgpGaXJzdCwgbGV0cyB2aXN1YWxpc2UgdGhlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIGNlbGxzOgpgYGB7cn0KSWRlbnRzKHNldXJhdCkgPC0gIlNDVF9zbm5fcmVzLjAuNyIKSW1hZ2VEaW1QbG90KHNldXJhdCwgY2VsbHMgPSBXaGljaENlbGxzKHNldXJhdCwgIGV4cHJlc3Npb249U0NUX3Nubl9yZXMuMC43ICVpbiUgYyggOSwgMCkpKQpgYGAKCldlIGNhbiB1c2UgRmluZE1hcmtlcnMgZnVuY3Rpb24gdG8gaWRlbnRpZnkgZGlmZmVyZW5jZXMgYmV0d2VlbiBhZGphY2VudCBlcGl0aGVsaWFsIGNlbGxzIGZvciB0aGVzZSBncm91cHMsIGlmIHdlIHJ1biBpdCBvbiB0aGUgYWdncmVnYXRlZCBFUEkgbWF0cml4OgpgYGB7cn0KbWFya2VycyA8LSBGaW5kTWFya2VycyhzZXVyYXQsIDAsIDkpIAptYXJrZXJzCmBgYAoKTGV0J3MgdmlzdWFsaXNlIHNvbWUgb2YgdGhlIHRvcCBoaXRzIC0gd2UgY2FuIHNlZSB0aGV5IHNlcGFyYXRlIG5pY2VseSBpbiBlcGl0aGVsaWFsIGNlbGxzOgpgYGB7cn0KVmxuUGxvdChzZXVyYXQsIGMoIkNBNyIsICAiU01PQzIiKSwgaWRlbnRzID0gYygwLCA5KSwgYXNzYXkgPSAiRVBJIiwgcHQuc2l6ZSA9IC4xLCBhbHBoYSA9IDAuNSkKYGBgCkFzIGEgc2FuaXR5IGNoZWNrLCBpZiB3ZSB2aXN1YWxpc2UgYWN0dWFsLCBub3QgZXBpLWFkamFjZW50IGV4cHJlc3Npb24gaW4gbXlvZmlicm9ibGFzdHMgYW5kIG1hY3JvcGhhZ2VzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUncyBoYXJkbHkgYW55IGV4cHJlc3Npb24gYXQgYWxsLiBTbywgd2UgYXJlIHBpY2tpbmcgdXAgZXBpdGhlbGlhbCBzaWduYWwKCmBgYHtyfQpWbG5QbG90KHNldXJhdCwgYygiQ0E3IiwgICJTTU9DMiIpLCBpZGVudHMgPSBjKDAsIDkpLCBhc3NheSA9ICJTQ1QiLCBwdC5zaXplID0gLjEsIGFscGhhID0gMC41KQpgYGAKQW5kIGEgdmlzdWFsIGNoZWNrCmBgYHtyfQpEZWZhdWx0QXNzYXkoc2V1cmF0KSA8LSAiU0NUIgpJbWFnZUZlYXR1cmVQbG90KHNldXJhdCwgIkNBNyIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdCwgIlNNT0MyIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYApGaW5hbGx5LCBsZXRzIHNhdmUgYWxsIG9mIG91ciBhbmFseXNpcyBhcyBhbiBSRFMgb2JqZWN0CmBgYHtyfQpzYXZlUkRTKHNldXJhdCwgZmlsZT0iY29sb25faW5fc2l0dS5SRFMiKQpgYGAKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoKYGBge3J9CgojIyNoZXJlIHdlIHBsb3QgYSBwZXJjZW50YWdlIHByb3BvcnRpb25zIG9mIGVhY2ggY2VsbCB0eXBlIGZyb20gaW50ZWdyYXRlZCBkYXRhc2V0cyAKCmxpYnJhcnkoU2V1cmF0KSAKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQogCm1ldGEuZGF0YSA8LSBDUkNfbWVyZ2VbW11dCiAKIyBDYWxjdWxhdGUgcGVyY2VudGFnZQpwZXJjZW50YWdlcyA8LSBtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkob3JpZy5pZGVudCkgJT4lCiAgbXV0YXRlKHRvdGFsX2NvdW50ID0gbigpKSAlPiUgICMgR2V0IHRvdGFsIGNvdW50cyBwZXIgb3JpZy5pZGVudAogIGdyb3VwX2J5KHByZWRpY3RlZC5pZCwgb3JpZy5pZGVudCkgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpLAogICAgICAgICAgICBwZXJjZW50YWdlID0gKGNvdW50IC8gZmlyc3QodG90YWxfY291bnQpKSAqIDEwMCkgICMgQ2FsY3VsYXRlIHBlcmNlbnRhZ2UKIAojIFBsb3QgcGVyY2VudGFnZXMgd2l0aCBsYWJlbHMKZ2dwbG90KHBlcmNlbnRhZ2VzLCBhZXMoeCA9IG9yaWcuaWRlbnQsIHkgPSBwZXJjZW50YWdlLCBmaWxsID0gcHJlZGljdGVkLmlkKSkgKwogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKHBlcmNlbnRhZ2UsIDEpKSwgICMgQWRkIHBlcmNlbnRhZ2UgbGFiZWxzIHJvdW5kZWQgdG8gMSBkZWNpbWFsCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLCBzaXplID0gMykgKyAgIyBQb3NpdGlvbiBsYWJlbHMgaW5zaWRlIGJhcnMKICB5bGFiKCJQZXJjZW50YWdlIikgKwogIGdndGl0bGUoIlBlcmNlbnRhZ2Ugb2YgUHJlZGljdGVkIElEIGJ5IE9yaWcgSWRlbnQiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKCg==